PageRenderTime 65ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/includes/CCTM.php

http://wordpress-custom-content-type-manager.googlecode.com/
PHP | 1699 lines | 1135 code | 163 blank | 401 comment | 96 complexity | 6d050238cf0ba2c7e6af35e00f31be6d MD5 | raw file
  1. <?php
  2. /*------------------------------------------------------------------------------
  3. CCTM = Custom Content Type Manager
  4. This is the main class for the Custom Content Type Manager plugin.
  5. This class handles the creation and management of custom post-types (also
  6. referred to as 'content-types'). It requires the FormGenerator.php,
  7. StandardizedCustomFields.php, and the CCTMtests.php files/classes to work.
  8. ------------------------------------------------------------------------------*/
  9. class CCTM
  10. {
  11. const name = 'Custom Content Type Manager';
  12. const txtdomain = 'custom-content-type-mgr'; // used for localization
  13. // Required versions (referenced in the CCTMtest class).
  14. const wp_req_ver = '3.0.1';
  15. const php_req_ver = '5.2.6';
  16. const mysql_req_ver = '5.0.41';
  17. // Used to uniquely identify an option_name in the wp_options table
  18. // ALL data describing the post types and their custom fields lives there.
  19. // DELETE FROM `wp_options` WHERE option_name='custom_content_types_mgr_data';
  20. // would clean out everything this plugin knows.
  21. const db_key = 'custom_content_types_mgr_data';
  22. // Used to uniquely identify this plugin's menu page in the WP manager
  23. const admin_menu_slug = 'custom_content_type_mgr';
  24. // These parameters identify where in the $_GET array we can find the values
  25. // and how URLs are constructed, e.g. some-admin-page.php?a=123&pt=xyz
  26. const action_param = 'a';
  27. const post_type_param = 'pt';
  28. // integer iterator used to uniquely identify groups of field definitions for
  29. // CSS and $_POST variables
  30. public static $def_i = 0;
  31. // Built-in post-types that can have custom fields, but cannot be deleted.
  32. public static $built_in_post_types = array('post','page');
  33. // Names that are off-limits for custom post types b/c they're already used by WP
  34. public static $reserved_post_types = array('post','page','attachment','revision'
  35. ,'nav_menu','nav_menu_item');
  36. // Custom field names are not allowed to use the same names as any column in wp_posts
  37. public static $reserved_field_names = array('ID','post_author','post_date','post_date_gmt',
  38. 'post_content','post_title','post_excerpt','post_status','comment_status','ping_status',
  39. 'post_password','post_name','to_ping','pinged','post_modified','post_modified_gmt',
  40. 'post_content_filtered','post_parent','guid','menu_order','post_type','post_mime_type',
  41. 'comment_count');
  42. // Future-proofing: post-type names cannot begin with 'wp_'
  43. // See: http://codex.wordpress.org/Custom_Post_Types
  44. // FUTURE: List any other reserved prefixes here (if any)
  45. public static $reserved_prefixes = array('wp_');
  46. public static $Errors; // used to store WP_Error object (FUTURE TODO)
  47. /*------------------------------------------------------------------------------
  48. This var stores the big definition for the forms that allow users to define
  49. custom post-types. The form is generated in a way so that when it is posted, it
  50. can be easily passed to WP's register_post_type() function.
  51. We populate the value via the setter function, _set_post_type_form_definition(),
  52. but we do not have a getter. Since we are lazy, and PHP doesn't require
  53. getters/setters, we would have forgone the setter function if possible, but we
  54. had to use a setter simply to avoid the PHP syntax errors that would have
  55. errupted had we tried something like this:
  56. public $myvar = array( 'val' => __('somevalue') );
  57. ------------------------------------------------------------------------------*/
  58. public static $post_type_form_definition = array();
  59. /*------------------------------------------------------------------------------
  60. This array defines the form used for all new custom field definitions.
  61. The variable is populated via a setter: _set_custom_field_def_template() for
  62. the same reason as the $post_type_form_definition var above (see above).
  63. See the _page_manage_custom_fields() function for when and how these forms
  64. are used and handled.
  65. ------------------------------------------------------------------------------*/
  66. public static $custom_field_def_template = array();
  67. //! Private Functions
  68. /*------------------------------------------------------------------------------
  69. Generate HTML portion of our manage custom fields form. This is in distinction
  70. to the JS portion of the form, which uses a slightly different format.
  71. self::$def_i is used to track the definition #. All 5 output fields will use
  72. the same $def_i number to identify their place in the $_POST array.
  73. INPUT: $custom_field_defs (mixed) an array of hashes, each hash describing
  74. a custom field.
  75. Array
  76. (
  77. [1] => Array
  78. (
  79. [label] => Rating
  80. [name] => rating
  81. [description] => MPAA rating
  82. [type] => dropdown
  83. [options] => Array
  84. (
  85. [0] => G
  86. [1] => PG
  87. [2] => PG-13
  88. )
  89. [sort_param] =>
  90. )
  91. )
  92. OUTPUT: An HTML form, length depends on the # of field defs.
  93. ------------------------------------------------------------------------------*/
  94. private static function _get_html_field_defs($custom_field_defs)
  95. {
  96. // print_r($def); exit;
  97. $output = '';
  98. foreach ($custom_field_defs as $def)
  99. {
  100. FormGenerator::$before_elements = '
  101. <div id="generated_form_number_'.self::$def_i.'">';
  102. FormGenerator::$after_elements = '
  103. <span class="button custom_content_type_mgr_remove" onClick="javascript:removeDiv(this.parentNode.id);">'.__('Remove This Field').'</span>
  104. <hr/>
  105. </div>';
  106. $translated = self::_transform_data_structure_for_editing($def);
  107. $output .= FormGenerator::generate($translated);
  108. self::$def_i++;
  109. }
  110. return $output;
  111. }
  112. /*------------------------------------------------------------------------------
  113. Gets a field definition ready for use inside of a JS variable. We have to over-
  114. ride some of the names used by the _get_html_field_defs() function so the
  115. resulting HTML/Javascript will inherit values from Javascript variables dynamically
  116. as the user adds new form fields on the fly.
  117. Here +def_i+ represents a JS concatenation, where def_i is a JS variable.
  118. ------------------------------------------------------------------------------*/
  119. private static function _get_javascript_field_defs()
  120. {
  121. $def = self::$custom_field_def_template;
  122. foreach ($def as $row_id => &$field)
  123. {
  124. $name = $row_id;
  125. // alter the Extra part of this for the listener on the dropdown
  126. if($name == 'type')
  127. {
  128. $field['extra'] = str_replace('[+def_i+]', 'def_i', $field['extra']);
  129. }
  130. $field['name'] = "custom_fields['+def_i+'][$name]";
  131. }
  132. FormGenerator::$before_elements = '<div id="generated_form_number_\'+def_i+\'">';
  133. FormGenerator::$after_elements = '
  134. <a class="button" href="#" onClick="javascript:removeDiv(this.parentNode.id);">'
  135. .__('Remove This Field', CCTM::txtdomain).'</a>
  136. <hr/>
  137. </div>';
  138. $output = FormGenerator::generate($def);
  139. // Javascript chokes on newlines...
  140. return str_replace( array("\r\n", "\r", "\n", "\t"), ' ', $output);
  141. }
  142. /*------------------------------------------------------------------------------
  143. Designed to safely retrieve scalar elements out of a hash. Don't use this
  144. if you have a more deeply nested object (e.g. an array of arrays).
  145. INPUT:
  146. $hash : an associative array, e.g. array('animal' => 'Cat');
  147. $key : the key to search for in that array, e.g. 'animal'
  148. $default (optional) : value to return if the value is not set. Default=''
  149. OUTPUT: either safely escaped value from the hash or the default value
  150. ------------------------------------------------------------------------------*/
  151. private static function _get_value($hash, $key, $default='')
  152. {
  153. if ( !isset($hash[$key]) )
  154. {
  155. return $default;
  156. }
  157. else
  158. { // Warning: stripslashes was added to avoid some weird behavior
  159. return esc_html(stripslashes($hash[$key]));
  160. }
  161. }
  162. /*------------------------------------------------------------------------------
  163. SYNOPSIS: checks the custom content data array to see if $post_type exists.
  164. The $data array is structured something like this:
  165. $data = array(
  166. 'movie' => array('name'=>'movie', ... ),
  167. 'book' => array('name'=>'book', ... ),
  168. ...
  169. );
  170. So we can just check the keys of the main array to see if the post type exists.
  171. Built-in post types 'page' and 'post' are considered valid (i.e. existing) by
  172. default, even if they haven't been explicitly defined for use by this plugin
  173. so long as the 2nd argument, $search_built_ins, is not overridden to false.
  174. INPUT:
  175. $post_type (string) the lowercase database slug identifying a post type.
  176. $search_built_ins (boolean) whether or not to search inside the
  177. $built_in_post_types array.
  178. OUTPUT: boolean true | false indicating whether this is a valid post-type
  179. ------------------------------------------------------------------------------*/
  180. private static function _is_existing_post_type($post_type, $search_built_ins=true)
  181. {
  182. $data = get_option( self::db_key );
  183. // If there is no existing data, check against the built-ins
  184. if ( empty($data) && $search_built_ins )
  185. {
  186. return in_array($post_type, self::$built_in_post_types);
  187. }
  188. // If there's no existing $data and we omit the built-ins...
  189. elseif ( empty($data) && !$search_built_ins )
  190. {
  191. return false;
  192. }
  193. // Check to see if we've stored this $post_type before
  194. elseif ( array_key_exists($post_type, $data) )
  195. {
  196. return true;
  197. }
  198. // Check the built-ins
  199. elseif ( $search_built_ins && in_array($post_type, self::$built_in_post_types) )
  200. {
  201. return true;
  202. }
  203. else
  204. {
  205. return false;
  206. }
  207. }
  208. /*------------------------------------------------------------------------------
  209. Manager Page -- called by page_main_controller()
  210. Activating a post type will cause it to show up in the WP menus and its custom
  211. fields will be managed.
  212. ------------------------------------------------------------------------------*/
  213. private static function _page_activate_post_type($post_type)
  214. {
  215. // Validate post type
  216. if (!self::_is_existing_post_type($post_type) )
  217. {
  218. self::_page_display_error();
  219. return;
  220. }
  221. // get current values from database (if any)
  222. $data = get_option( self::db_key, array() );
  223. $data[$post_type]['is_active'] = 1;
  224. update_option( self::db_key, $data );
  225. // Often, PHP scripts use the header() function to refresh a page, but
  226. // WP has already claimed those, so we use a JavaScript refresh instead.
  227. // Refreshing the page ensures that active post types are added to menus.
  228. $msg = '
  229. <script type="text/javascript">
  230. window.location.replace("?page='.self::admin_menu_slug.'");
  231. </script>';
  232. // I think this $msg gets lost, but future TODO: use a 'flash' msg to display it
  233. // even after the page refresh.
  234. self::_page_show_all_post_types($msg);
  235. }
  236. /*------------------------------------------------------------------------------
  237. Manager Page -- called by page_main_controller()
  238. Create a new post type
  239. ------------------------------------------------------------------------------*/
  240. private static function _page_create_new_post_type()
  241. {
  242. // Variables for our template (I'm cheating here, loading in-line styles
  243. // becase the enqueue stuff is too heavy). TODO: use enqueue function to
  244. // load this only on the required pages.
  245. $style = '<style>'
  246. . file_get_contents( self::get_basepath() .'/css/create_or_edit_post_type.css' )
  247. . '</style>';
  248. $page_header = __('Create Custom Content Type', CCTM::txtdomain);
  249. $fields = '';
  250. $action_name = 'custom_content_type_mgr_create_new_content_type';
  251. $nonce_name = 'custom_content_type_mgr_create_new_content_type_nonce';
  252. $submit = __('Create New Content Type', CCTM::txtdomain);
  253. $msg = '';
  254. $def = self::$post_type_form_definition;
  255. // Save data if it was properly submitted
  256. if ( !empty($_POST) && check_admin_referer($action_name,$nonce_name) )
  257. {
  258. $sanitized_vals = self::_sanitize_post_type_def($_POST);
  259. $error_msg = self::_post_type_name_has_errors($sanitized_vals['post_type']);
  260. if ( empty($error_msg) )
  261. {
  262. self::_save_post_type_settings($sanitized_vals);
  263. $msg = '
  264. <div class="updated">
  265. <p>'
  266. . sprintf( __('The content type %s has been created', CCTM::txtdomain), '<em>'.$sanitized_vals['post_type'].'</em>')
  267. . '</p>
  268. </div>';
  269. self::_page_show_all_post_types($msg);
  270. return;
  271. }
  272. else
  273. {
  274. // This is for repopulating the form
  275. foreach ( $def as $node_id => $d )
  276. {
  277. $d['value'] = self::_get_value($sanitized_vals, $d['name']);
  278. }
  279. $msg = "<div class='error'>$error_msg</div>";
  280. }
  281. }
  282. $fields = FormGenerator::generate($def);
  283. include('pages/basic_form.php');
  284. }
  285. /*------------------------------------------------------------------------------
  286. Manager Page -- called by page_main_controller()
  287. Deactivate a post type. This will remove custom post types from the WP menus;
  288. deactivation stops custom fields from being standardized in built-in and custom
  289. post types
  290. ------------------------------------------------------------------------------*/
  291. private static function _page_deactivate_post_type($post_type)
  292. {
  293. // Validate post type
  294. if (!self::_is_existing_post_type($post_type) )
  295. {
  296. self::_page_display_error();
  297. return;
  298. }
  299. // Variables for our template
  300. $style = '';
  301. $page_header = sprintf( __('Deactivate Content Type %s', CCTM::txtdomain), $post_type );
  302. $fields = '';
  303. $action_name = 'custom_content_type_mgr_deactivate_content_type';
  304. $nonce_name = 'custom_content_type_mgr_deactivate_content_type_nonce';
  305. $submit = __('Deactivate', CCTM::txtdomain);
  306. // If properly submitted, Proceed with deleting the post type
  307. if ( !empty($_POST) && check_admin_referer($action_name,$nonce_name) )
  308. {
  309. // get current values from database
  310. $data = get_option( self::db_key, array() );
  311. $data[$post_type]['is_active'] = 0;
  312. update_option( self::db_key, $data );
  313. // A JavaScript refresh ensures that inactive post types are removed from the menus.
  314. $msg = '
  315. <script type="text/javascript">
  316. window.location.replace("?page='.self::admin_menu_slug.'");
  317. </script>';
  318. self::_page_show_all_post_types($msg);
  319. return;
  320. }
  321. $msg = '<div class="error"><p>'
  322. . sprintf( __('You are about to deactivate the %s post type. Deactivation does not delete anything.', CCTM::txtdomain ), "<em>$post_type</em>")
  323. .'</p>';
  324. // If it's a custom post type, we include some additional info.
  325. if ( !in_array($post_type, self::$built_in_post_types) )
  326. {
  327. $msg .= '<p>'
  328. . sprintf( __('After deactivation, %s posts will be unavailable to the outside world. %s will be removed from the administration menus and you will no longer be able to edit them using the WordPress manager.', CCTM::txtdomain), $post_type, "<strong>$post_type</strong>" )
  329. .'</p>';
  330. }
  331. $post_cnt_obj = wp_count_posts($post_type);
  332. $msg .= '<p>'
  333. . sprintf( __('This would affect %1$s published %2$s sposts.'
  334. ,CCTM::txtdomain), '<strong>'.$post_cnt_obj->publish.'</strong>'
  335. , "<em>$post_type</em>")
  336. .'</p>';
  337. $msg .= '<p>'.__('Are you sure you want to do this?',CCTM::txtdomain).'</p>
  338. </div>';
  339. include('pages/basic_form.php');
  340. }
  341. /*------------------------------------------------------------------------------
  342. Manager Page -- called by page_main_controller()
  343. This is only a valid page for custom post types.
  344. ------------------------------------------------------------------------------*/
  345. private static function _page_delete_post_type($post_type)
  346. {
  347. // We can't delete built-in post types
  348. if (!self::_is_existing_post_type($post_type, false ) )
  349. {
  350. self::_page_display_error();
  351. return;
  352. }
  353. // Variables for our template
  354. $style = '';
  355. $page_header = sprintf( __('Delete Content Type: %s', CCTM::txtdomain), $post_type );
  356. $fields = '';
  357. $action_name = 'custom_content_type_mgr_delete_content_type';
  358. $nonce_name = 'custom_content_type_mgr_delete_content_type_nonce';
  359. $submit = __('Delete',CCTM::txtdomain);
  360. // If properly submitted, Proceed with deleting the post type
  361. if ( !empty($_POST) && check_admin_referer($action_name,$nonce_name) )
  362. {
  363. // get current values from database
  364. $data = get_option( self::db_key, array() );
  365. unset($data[$post_type]); // <-- Delete this node of the data structure
  366. update_option( self::db_key, $data );
  367. $msg = '<div class="updated"><p>'
  368. .sprintf( __('The post type %s has been deleted', CCTM::txtdomain), "<em>$post_type</em>")
  369. . '</p></div>';
  370. self::_page_show_all_post_types($msg);
  371. return;
  372. }
  373. $msg = '<div class="error"><p>'
  374. . sprintf( __('You are about to delete the %s post type. This will remove all of its settings from the database, but this will NOT delete any rows from the wp_posts table. However, without a custom post type defined for those rows, they will be essentially invisible to WordPress.', CCTM::txtdomain), "<em>$post_type</em>" )
  375. .'</p>'
  376. . '<p>'.__('Are you sure you want to do this?',CCTM::txtdomain).'</p>';
  377. include('pages/basic_form.php');
  378. }
  379. /*------------------------------------------------------------------------------
  380. Manager Page -- called by page_main_controller()
  381. Returned on errors. Future: accept an argument identifying an error
  382. ------------------------------------------------------------------------------*/
  383. private static function _page_display_error()
  384. {
  385. $msg = '<p>'. __('Invalid post type.', CCTM::txtdomain)
  386. . '</p><a class="button" href="?page='
  387. .self::admin_menu_slug.'">'. __('Back', CCTM::txtdomain). '</a>';
  388. wp_die( $msg );
  389. }
  390. /*------------------------------------------------------------------------------
  391. Manager Page -- called by page_main_controller()
  392. Edit an existing post type. Changing the unique post-type identifier (i.e. name)
  393. is not allowed.
  394. ------------------------------------------------------------------------------*/
  395. private static function _page_edit_post_type($post_type)
  396. {
  397. // We can't edit built-in post types
  398. if (!self::_is_existing_post_type($post_type, false ) )
  399. {
  400. self::_page_display_error();
  401. return;
  402. }
  403. // Variables for our template (TODO: register this instead of this cheap inline trick)
  404. $style = '<style>'
  405. . file_get_contents( self::get_basepath() .'/css/create_or_edit_post_type_class.css' )
  406. . '</style>';
  407. $page_header = __('Edit Content Type: ') . $post_type;
  408. $fields = '';
  409. $action_name = 'custom_content_type_mgr_edit_content_type';
  410. $nonce_name = 'custom_content_type_mgr_edit_content_type_nonce';
  411. $submit = __('Save',CCTM::txtdomain);
  412. $msg = ''; // Any validation errors
  413. $def = self::$post_type_form_definition;
  414. // Save data if it was properly submitted
  415. if ( !empty($_POST) && check_admin_referer($action_name,$nonce_name) )
  416. {
  417. $sanitized_vals = self::_sanitize_post_type_def($_POST);
  418. $error_msg = self::_post_type_name_has_errors($sanitized_vals['post_type']);
  419. if ( empty($error_msg) )
  420. {
  421. self::_save_post_type_settings($sanitized_vals);
  422. $msg = '
  423. <script type="text/javascript">
  424. window.location.replace("?page='.self::admin_menu_slug.'");
  425. </script>';
  426. $msg .= '<div class="updated"><p>'
  427. . sprintf( __('Settings for %s have been updated.', CCTM::txtdomain )
  428. , '<em>'.$sanitized_vals['post_type'].'</em>')
  429. .'</p></div>';
  430. self::_page_show_all_post_types($msg); // TODO: make this message persist across page refreshes
  431. return;
  432. }
  433. else
  434. {
  435. // This is for repopulating the form
  436. $def = self::_populate_form_def_from_data($def, $sanitized_vals);
  437. $msg = "<div class='error'>$error_msg</div>";
  438. }
  439. }
  440. // get current values from database
  441. $data = get_option( self::db_key, array() );
  442. // Populate the form $def with values from the database
  443. $def = self::_populate_form_def_from_data($def, $data[$post_type]);
  444. $fields = FormGenerator::generate($def);
  445. include('pages/basic_form.php');
  446. }
  447. /*------------------------------------------------------------------------------
  448. Manager Page -- called by page_main_controller()
  449. Manage custom fields for any post type, built-in or custom.
  450. ------------------------------------------------------------------------------*/
  451. private static function _page_manage_custom_fields($post_type)
  452. {
  453. // Validate post type
  454. if (!self::_is_existing_post_type($post_type) )
  455. {
  456. self::_page_display_error();
  457. return;
  458. }
  459. $action_name = 'custom_content_type_mgr_manage_custom_fields';
  460. $nonce_name = 'custom_content_type_mgr_manage_custom_fields_nonce';
  461. $msg = ''; // Any validation errors
  462. $def_cnt = ''; // # of custom field definitions
  463. // The set of fields that makes up a custom field definition, but stripped of newlines
  464. // and with some modifications so it can be used inside a javascript variable
  465. $new_field_def_js = '';
  466. // Existing fields
  467. $fields = '';
  468. $data = get_option( self::db_key, array() );
  469. // Validate/Save data if it was properly submitted
  470. if ( !empty($_POST) && check_admin_referer($action_name,$nonce_name) )
  471. {
  472. $error_msg = array(); // used as a flag
  473. if (!isset($_POST['custom_fields']))
  474. {
  475. $data[$post_type]['custom_fields'] = array(); // all custom fields were deleted
  476. }
  477. else
  478. {
  479. $data[$post_type]['custom_fields'] = $_POST['custom_fields'];
  480. foreach ( $data[$post_type]['custom_fields'] as &$cf )
  481. {
  482. if ( preg_match('/[^a-z_]/i', $cf['name']))
  483. {
  484. $error_msg[] = sprintf(
  485. __('%s contains invalid characters.',CCTM::txtdomain)
  486. , '<strong>'.$cf['name'].'</strong>');
  487. $cf['name'] = preg_replace('/[^a-z_]/','',$cf['name']);
  488. }
  489. if ( strlen($cf['name']) > 20 )
  490. {
  491. $cf['name'] = substr($cf['name'], 0 , 20);
  492. $error_msg[] = sprintf(
  493. __('%s is too long.',CCTM::txtdomain)
  494. , '<strong>'.$cf['name'].'</strong>');
  495. }
  496. if ( in_array($cf['name'], self::$reserved_field_names ) )
  497. {
  498. $error_msg[] = sprintf(
  499. __('%s is a reserved name.',CCTM::txtdomain)
  500. , '<strong>'.$cf['name'].'</strong>');
  501. }
  502. }
  503. }
  504. if ($error_msg)
  505. {
  506. foreach ( $error_msg as &$e )
  507. {
  508. $e = '<li>'.$e.'</li>';
  509. }
  510. $msg = sprintf('<div class="error">
  511. <h3>%1$s</h3>
  512. %2$s %3$s %4$s
  513. <ul style="margin-left:30px">
  514. %5$s
  515. </ul>
  516. </div>'
  517. , __('There were errors in the names of your custom fields.', CCTM::txtdomain)
  518. , __('Names must not exceed 20 characters in length.', CCTM::txtdomain)
  519. , __('Names may contain the letters a-z and underscores only.', CCTM::txtdomain)
  520. , __('You cannot name your field using any reserved name.', CCTM::txtdomain)
  521. , implode("\n", $error_msg)
  522. );
  523. }
  524. else
  525. {
  526. update_option( self::db_key, $data );
  527. $msg = sprintf('<div class="updated">%s</p></div>'
  528. , sprintf(__('Custom fields for %s have been updated', CCTM::txtdomain)
  529. , '<em>'.$post_type.'</em>'
  530. )
  531. );
  532. }
  533. }
  534. // We want to extract a $def for only THIS content_type's custom_fields
  535. $def = array();
  536. if ( isset($data[$post_type]['custom_fields']) )
  537. {
  538. $def = $data[$post_type]['custom_fields'];
  539. }
  540. // count # of custom field definitions --> replaces [+def_i+]
  541. $def_cnt = count($def);
  542. // We don't need the exact number of form elements, we just need an integer
  543. // that is sufficiently high so that the ids of Javascript-created elements
  544. // do not conflict with the ids of PHP-created elements.
  545. $element_cnt = count($def, COUNT_RECURSIVE);
  546. if (!$def_cnt)
  547. {
  548. $x = sprintf( __('The %s post type does not have any custom fields yet.', CCTM::txtdomain)
  549. , "<em>$post_type</em>" );
  550. $y = __('Click the button above to add a custom field.', CCTM::txtdomain );
  551. $msg .= sprintf('<div class="updated">%s %s</div>', $x, $y);
  552. }
  553. $fields = self::_get_html_field_defs($def);
  554. // Gets a form definition ready for use inside of a JS variable
  555. $new_field_def_js = self::_get_javascript_field_defs();
  556. include('pages/manage_custom_fields.php');
  557. }
  558. /*------------------------------------------------------------------------------
  559. Manager Page -- called by page_main_controller()
  560. Show what a single page for this custom post-type might look like. This is
  561. me throwing a bone to template editors and creators.
  562. I'm using a tpl and my parse() function because I got to print out sample PHP
  563. code and it's too much of a pain in the ass to include PHP without it executing.
  564. ------------------------------------------------------------------------------*/
  565. private static function _page_sample_template($post_type)
  566. {
  567. // Validate post type
  568. if (!self::_is_existing_post_type($post_type) )
  569. {
  570. self::_page_display_error();
  571. return;
  572. }
  573. $current_theme_name = get_current_theme();
  574. $current_theme_path = get_stylesheet_directory();
  575. $hash = array();
  576. $data = get_option( self::db_key, array() );
  577. $tpl = file_get_contents( CCTM_PATH.'/tpls/sample_template_code.tpl');
  578. $tpl = htmlentities($tpl);
  579. $msg = sprintf( __('WordPress supports a custom theme file for each registered post-type (content-type). Copy the text below into a file named <strong>%s</strong> and save it into your active theme.', CCTM::txtdomain)
  580. , 'single-'.$post_type.'.php'
  581. );
  582. $msg .= sprintf( __('You are currently using the %1$s theme. Save the file into the %2$s directory.',CCTM::txtdomain)
  583. , '<strong>'.$current_theme_name.'</strong>'
  584. , '<strong>'.$current_theme_path.'</strong>'
  585. );
  586. $data = get_option( self::db_key, array() );
  587. $def = array();
  588. if ( isset($data[$post_type]['custom_fields']) )
  589. {
  590. $def = $data[$post_type]['custom_fields'];
  591. }
  592. //print_r($def); exit;
  593. $custom_fields_str = '';
  594. foreach ( $def as $d )
  595. {
  596. $custom_fields_str .= sprintf("\t\t<strong>%s:</strong> <?php print_custom_field('%s'); ?><br />\n", $d['label'], $d['name']);
  597. }
  598. // Populate placeholders
  599. $hash['post_type'] = $post_type;
  600. $hash['custom_fields'] = $custom_fields_str;
  601. $sample_code = StandardizedCustomFields::parse($tpl, $hash);
  602. include('pages/sample_template.php');
  603. }
  604. /*------------------------------------------------------------------------------
  605. Manager Page -- called by page_main_controller()
  606. List all post types (default page)
  607. ------------------------------------------------------------------------------*/
  608. private static function _page_show_all_post_types($msg='')
  609. {
  610. $data = get_option( self::db_key, array() );
  611. $customized_post_types = array_keys($data);
  612. $displayable_types = array_merge(self::$built_in_post_types , $customized_post_types);
  613. $displayable_types = array_unique($displayable_types);
  614. $row_data = '';
  615. foreach ( $displayable_types as $post_type )
  616. {
  617. if ( isset($data[$post_type]['is_active']) && !empty($data[$post_type]['is_active']) )
  618. {
  619. $class = 'active';
  620. $is_active = true;
  621. }
  622. else
  623. {
  624. $class = 'inactive';
  625. $is_active = false;
  626. }
  627. // Built-in post types use a canned description.
  628. if ( in_array($post_type, self::$built_in_post_types) )
  629. {
  630. $description = __('Built-in post type');
  631. }
  632. // Whereas users define the description for custom post types
  633. else
  634. {
  635. $description = self::_get_value($data[$post_type],'description');
  636. }
  637. ob_start();
  638. include('single_content_type_tr.php');
  639. $row_data .= ob_get_contents();
  640. ob_end_clean();
  641. }
  642. include('pages/default.php');
  643. }
  644. /*------------------------------------------------------------------------------
  645. Populate form definition with data that defines a post-type. This data comes
  646. either from the database, or from the $_POST array. The $pt_data
  647. (i.e. post-type data) should contain only information about
  648. a single post_type; do not pass this function the entire contents of the
  649. get_option.
  650. This whole function is necessary because the form generator definition needs
  651. to know where to find values for its fields -- the $def is an empty template,
  652. so we need to populate with values by splicing the $def together with the
  653. $pt_data. Some of this complication is due to the fact that we need to update
  654. the field names to acommodate array, e.g.
  655. <input type="text" name="some[2][name]" />
  656. INPUT: $def (mixed) form definition
  657. $pt_data (mixed) data describing a single post type
  658. OUTPUT: $def updated with values
  659. ------------------------------------------------------------------------------*/
  660. private static function _populate_form_def_from_data($def, $pt_data)
  661. {
  662. foreach ($def as $node_id => $tmp)
  663. {
  664. if ( $node_id == 'supports_title' )
  665. {
  666. if ( !empty($pt_data['supports']) && in_array('title', $pt_data['supports']) )
  667. {
  668. $def[$node_id]['value'] = 'title';
  669. }
  670. else
  671. {
  672. $def[$node_id]['value'] = '';
  673. }
  674. }
  675. elseif ( $node_id == 'supports_editor' )
  676. {
  677. if ( !empty($pt_data['supports']) && in_array('editor', $pt_data['supports']) )
  678. {
  679. $def[$node_id]['value'] = 'editor';
  680. }
  681. else
  682. {
  683. $def[$node_id]['value'] = '';
  684. }
  685. }
  686. elseif ( $node_id == 'supports_author' )
  687. {
  688. if ( !empty($pt_data['supports']) && in_array('author', $pt_data['supports']) )
  689. {
  690. $def[$node_id]['value'] = 'author';
  691. }
  692. else
  693. {
  694. $def[$node_id]['value'] = '';
  695. }
  696. }
  697. elseif ( $node_id == 'supports_excerpt' )
  698. {
  699. if ( !empty($pt_data['supports']) && in_array('excerpt', $pt_data['supports']) )
  700. {
  701. $def[$node_id]['value'] = 'excerpt';
  702. }
  703. else
  704. {
  705. $def[$node_id]['value'] = '';
  706. }
  707. }
  708. elseif ( $node_id == 'supports_thumbnail' )
  709. {
  710. if ( !empty($pt_data['supports']) && in_array('thumbnail', $pt_data['supports']) )
  711. {
  712. $def[$node_id]['value'] = 'thumbnail';
  713. }
  714. else
  715. {
  716. $def[$node_id]['value'] = '';
  717. }
  718. }
  719. elseif ( $node_id == 'supports_trackbacks' )
  720. {
  721. if ( !empty($pt_data['supports']) && in_array('trackbacks', $pt_data['supports']) )
  722. {
  723. $def[$node_id]['value'] = 'trackbacks';
  724. }
  725. else
  726. {
  727. $def[$node_id]['value'] = '';
  728. }
  729. }
  730. elseif ( $node_id == 'supports_custom-fields' )
  731. {
  732. if ( !empty($pt_data['supports']) && in_array('custom-fields', $pt_data['supports']) )
  733. {
  734. $def[$node_id]['value'] = 'custom-fields';
  735. }
  736. else
  737. {
  738. $def[$node_id]['value'] = '';
  739. }
  740. }
  741. elseif ( $node_id == 'supports_comments' )
  742. {
  743. if ( !empty($pt_data['supports']) && in_array('comments', $pt_data['supports']) )
  744. {
  745. $def[$node_id]['value'] = 'comments';
  746. }
  747. else
  748. {
  749. $def[$node_id]['value'] = '';
  750. }
  751. }
  752. elseif ( $node_id == 'supports_revisions' )
  753. {
  754. if ( !empty($pt_data['supports']) && in_array('revisions', $pt_data['supports']) )
  755. {
  756. $def[$node_id]['value'] = 'revisions';
  757. }
  758. else
  759. {
  760. $def[$node_id]['value'] = '';
  761. }
  762. }
  763. elseif ( $node_id == 'supports_page-attributes' )
  764. {
  765. if ( !empty($pt_data['supports']) && in_array('page-attributes', $pt_data['supports']) )
  766. {
  767. $def[$node_id]['value'] = 'page-attributes';
  768. }
  769. else
  770. {
  771. $def[$node_id]['value'] = '';
  772. }
  773. }
  774. elseif ( $node_id == 'rewrite_slug' )
  775. {
  776. if ( !empty($pt_data['rewrite']['slug']) )
  777. {
  778. $def[$node_id]['value'] = $pt_data['rewrite']['slug'];
  779. }
  780. else
  781. {
  782. $def[$node_id]['value'] = '';
  783. }
  784. }
  785. elseif ( $node_id == 'rewrite_with_front' )
  786. {
  787. if ( !empty($pt_data['rewrite']['with_front']) )
  788. {
  789. $def[$node_id]['value'] = $pt_data['rewrite']['with_front'];
  790. }
  791. else
  792. {
  793. $def[$node_id]['value'] = '';
  794. }
  795. }
  796. else
  797. {
  798. $field_name = $def[$node_id]['name'];
  799. $def[$node_id]['value'] = self::_get_value($pt_data,$field_name);
  800. }
  801. }
  802. return $def;
  803. }
  804. /*------------------------------------------------------------------------------
  805. SYNOPSIS: Ensure that $post_type is a valid post_type name.
  806. INPUT:
  807. $post_type (str) name of the post type
  808. OUTPUT: null if there are no errors, otherwise return a string describing an error.
  809. ------------------------------------------------------------------------------*/
  810. private static function _post_type_name_has_errors($post_type)
  811. {
  812. $errors = null;
  813. $taxonomy_names_array = get_taxonomies('','names');
  814. if ( empty($post_type) )
  815. {
  816. return __('Name is required.', CCTM::txtdomain);
  817. }
  818. foreach ( self::$reserved_prefixes as $rp )
  819. {
  820. if ( preg_match('/^'.preg_quote($rp).'.*/', $post_type) )
  821. {
  822. return sprintf( __('The post type name cannot begin with %s because that is a reserved prefix.', CCTM::txtdomain)
  823. , $rp);
  824. }
  825. }
  826. // Is reserved name?
  827. if ( in_array($post_type, self::$reserved_post_types) )
  828. {
  829. $msg = __('Please choose another name.', CCTM::txtdomain );
  830. $msg .= ' ';
  831. $msg .= sprintf( __('%s is a reserved name.', CCTM::txtdomain )
  832. , '<strong>'.$post_type.'</strong>' );
  833. return $msg;
  834. }
  835. // Make sure the post-type name does not conflict with any registered taxonomies
  836. elseif ( in_array( $post_type, $taxonomy_names_array) )
  837. {
  838. $msg = __('Please choose another name.', CCTM::txtdomain );
  839. $msg .= ' ';
  840. $msg .= sprintf( __('%s is already in use as a registered taxonomy name.', CCTM::txtdomain)
  841. , $post_type );
  842. }
  843. // If this is a new post_type or if the $post_type name has been changed,
  844. // ensure that it is not going to overwrite an existing post type name.
  845. else
  846. {
  847. $data = get_option( self::db_key, array() );
  848. if ( in_array( $post_type, array_keys($data) ) )
  849. {
  850. return __('That name is already in use.');
  851. }
  852. }
  853. return; // no errors
  854. }
  855. /*------------------------------------------------------------------------------
  856. Every form element when creating a new post type must be filtered here.
  857. INPUT: unsanitized $_POST data.
  858. OUTPUT: filtered data. Only white-listed values are passed thru to output.
  859. ------------------------------------------------------------------------------*/
  860. private static function _sanitize_post_type_def($raw)
  861. {
  862. $sanitized = array();
  863. // This will be empty if none of the "supports" items are checked.
  864. if (!empty($raw['supports']) )
  865. {
  866. $sanitized['supports'] = $raw['supports'];
  867. }
  868. unset($raw['supports']); // we manually set this later
  869. // Temporary thing...
  870. unset($sanitized['rewrite_slug']);
  871. unset($sanitized['rewrite_with_front']);
  872. // We grab everything, then override specific $keys as needed.
  873. foreach ($raw as $key => $value )
  874. {
  875. if ( !preg_match('/^_.*/', $key) )
  876. {
  877. $sanitized[$key] = self::_get_value($raw, $key);
  878. }
  879. }
  880. // Specific overrides below:
  881. // post_type is the only required field
  882. $sanitized['post_type'] = self::_get_value($raw,'post_type');
  883. $sanitized['post_type'] = strtolower($sanitized['post_type']);
  884. $sanitized['post_type'] = preg_replace('/[^a-z|_]/', '_', $sanitized['post_type']);
  885. $sanitized['post_type'] = substr($sanitized['post_type'], 0, 20);
  886. // Our form passes integers and strings, but WP req's literal booleans,
  887. // so we do some type-casting here to ensure literal booleans.
  888. $sanitized['show_ui'] = (bool) self::_get_value($raw,'show_ui');
  889. $sanitized['public'] = (bool) self::_get_value($raw,'public');
  890. $sanitized['show_in_nav_menus'] = (bool) self::_get_value($raw,'show_in_nav_menus');
  891. $sanitized['can_export'] = (bool) self::_get_value($raw,'can_export');
  892. $sanitized['use_default_menu_icon'] = (bool) self::_get_value($raw,'use_default_menu_icon');
  893. $sanitized['hierarchical'] = (bool) self::_get_value($raw,'hierarchical');
  894. // *facepalm*... Special handling req'd here for menu_position because 0
  895. // is handled differently than a literal null.
  896. if ( (int) self::_get_value($raw,'menu_position') )
  897. {
  898. $sanitized['menu_position'] = (int) self::_get_value($raw,'menu_position',null);
  899. }
  900. else
  901. {
  902. $sanitized['menu_position'] = null;
  903. }
  904. // menu_icon... the user will lose any custom Menu Icon URL if they save with this checked!
  905. // TODO: let this value persist.
  906. if( $sanitized['use_default_menu_icon'] )
  907. {
  908. unset($sanitized['menu_icon']); // === null;
  909. }
  910. if (empty($sanitized['query_var']))
  911. {
  912. $sanitized['query_var'] = false;
  913. }
  914. // Rewrites. TODO: make this work like the built-in post-type permalinks
  915. switch ($sanitized['permalink_action'])
  916. {
  917. case '/%postname%/':
  918. $sanitized['rewrite'] = true;
  919. break;
  920. case 'Custom':
  921. $sanitized['rewrite']['slug'] = $raw['rewrite_slug'];
  922. $sanitized['rewrite']['with_front'] = (bool) $raw['rewrite_with_front'];
  923. break;
  924. case 'Off':
  925. default:
  926. $sanitized['rewrite'] = false;
  927. }
  928. return $sanitized;
  929. }
  930. /*------------------------------------------------------------------------------
  931. INPUT: $def (mixed) associative array describing a single post-type.
  932. OUTPUT: none; this saves a serialized data structure (arrays of arrays) to the db
  933. ------------------------------------------------------------------------------*/
  934. private static function _save_post_type_settings($def)
  935. {
  936. $key = $def['post_type'];
  937. $all_post_types = get_option( self::db_key, array() );
  938. // Update existing settings if this post-type has already been added
  939. if ( isset($all_post_types[$key]) )
  940. {
  941. $all_post_types[$key] = array_merge($all_post_types[$key], $def);
  942. }
  943. // OR, create a new node in the data structure for our new post-type
  944. else
  945. {
  946. $all_post_types[$key] = $def;
  947. }
  948. update_option( self::db_key, $all_post_types );
  949. }
  950. /*------------------------------------------------------------------------------
  951. This is sorta a reflexive form definition: it defines the form required to
  952. define a form. Note that names imply arrays, e.g. name="custom_fields[3][label]".
  953. This is intentional: since all custom field definitions are stored as a serialized
  954. array in the wp_options table, we have to treat all defs as a kind of recordset
  955. (i.e. an array of similar hashes).
  956. [+def_i+] gets used by Javascript for on-the-fly adding of form fields (where
  957. def_i is a Javascript variable indicating the definition number (or i for integer).
  958. ------------------------------------------------------------------------------*/
  959. private static function _set_custom_field_def_template()
  960. {
  961. $def['label']['name'] = 'custom_fields[[+def_i+]][label]';
  962. $def['label']['label'] = __('Label', CCTM::txtdomain);
  963. $def['label']['value'] = '';
  964. $def['label']['extra'] = '';
  965. $def['label']['description'] = '';
  966. $def['label']['type'] = 'text';
  967. $def['label']['sort_param'] = 1;
  968. $def['name']['name'] = 'custom_fields[[+def_i+]][name]';
  969. $def['name']['label'] = __('Name', CCTM::txtdomain);
  970. $def['name']['value'] = '';
  971. $def['name']['extra'] = '';
  972. $def['name']['description'] = __('The name identifies the option_name in the wp_postmeta database table. You will use this name in your template functions to identify this custom field.', CCTM::txtdomain);
  973. $def['name']['type'] = 'text';
  974. $def['name']['sort_param'] = 2;
  975. $def['description']['name'] = 'custom_fields[[+def_i+]][description]';
  976. $def['description']['label'] = __('Description',CCTM::txtdomain);
  977. $def['description']['value'] = '';
  978. $def['description']['extra'] = '';
  979. $def['description']['description'] = '';
  980. $def['description']['type'] = 'textarea';
  981. $def['description']['sort_param'] = 3;
  982. $def['type']['name'] = 'custom_fields[[+def_i+]][type]';
  983. $def['type']['label'] = __('Input Type', CCTM::txtdomain);
  984. $def['type']['value'] = 'text';
  985. $def['type']['extra'] = ' onchange="javascript:addRemoveDropdown(this.parentNode.id,this.value, [+def_i+])"';
  986. $def['type']['description'] = '';
  987. $def['type']['type'] = 'dropdown';
  988. $def['type']['options'] = array('checkbox','dropdown','media','relation','text','textarea','wysiwyg');
  989. $def['type']['sort_param'] = 4;
  990. $def['sort_param']['name'] = 'custom_fields[[+def_i+]][sort_param]';
  991. $def['sort_param']['label'] = __('Sort Order',CCTM::txtdomain);
  992. $def['sort_param']['value'] = '';
  993. $def['sort_param']['extra'] = ' size="2" maxlength="4"';
  994. $def['sort_param']['description'] = __('This controls where this field will appear on the page. Fields with smaller numbers will appear higher on the page.',CCTM::txtdomain);
  995. $def['sort_param']['type'] = 'text';
  996. $def['sort_param']['sort_param'] = 5;
  997. self::$custom_field_def_template = $def;
  998. }
  999. /*------------------------------------------------------------------------------
  1000. Used when creating or editing Post Types
  1001. ------------------------------------------------------------------------------*/
  1002. private static function _set_post_type_form_definition()
  1003. {
  1004. $def = array();
  1005. $def['post_type']['name'] = 'post_type';
  1006. $def['post_type']['label'] = __('Name', CCTM::txtdomain). ' *';
  1007. $def['post_type']['value'] = '';
  1008. $def['post_type']['extra'] = '';
  1009. $def['post_type']['description'] = __('Unique singular name to identify this post type in the database, e.g. "movie","book". This may show up in your URLs, e.g. ?movie=star-wars. This will also make a new theme file available, starting with prefix named "single-", e.g. <strong>single-movie.php</strong>. The name should be lowercase with only letters and underscores. This name cannot be changed!', CCTM::txtdomain);
  1010. $def['post_type']['type'] = 'text';
  1011. $def['post_type']['sort_param'] = 1;
  1012. $def['singular_label']['name'] = 'singular_label';
  1013. $def['singular_label']['label'] = __('Singular Label', CCTM::txtdomain);
  1014. $def['singular_label']['value'] = '';
  1015. $def['singular_label']['extra'] = '';
  1016. $def['singular_label']['description'] = __('Human readable single instance of this content type, e.g. "Post"', CCTM::txtdomain);
  1017. $def['singular_label']['type'] = 'text';
  1018. $def['singular_label']['sort_param'] = 2;
  1019. $def['label']['name'] = 'label';
  1020. $def['label']['label'] = __('Menu Label (Plural)', CCTM::txtdomain);
  1021. $def['label']['value'] = '';
  1022. $def['label']['extra'] = '';
  1023. $def['label']['description'] = __('Plural name used in the admin menu, e.g. "Posts"', CCTM::txtdomain);
  1024. $def['label']['type'] = 'text';
  1025. $def['label']['sort_param'] = 3;
  1026. $def['description']['name'] = 'description';
  1027. $def['description']['label'] = __('Description', CCTM::txtdomain);
  1028. $def['description']['value'] = '';
  1029. $def['description']['extra'] = '';
  1030. $def['description']['description'] = '';
  1031. $def['description']['type'] = 'textarea';
  1032. $def['description']['sort_param'] = 4;
  1033. $def['show_ui']['name'] = 'show_ui';
  1034. $def['show_ui']['label'] = __('Show Admin User Interface', CCTM::txtdomain);
  1035. $def['show_ui']['value'] = '1';
  1036. $def['show_ui']['extra'] = '';
  1037. $def['show_ui']['description'] = __('Should this post type be visible on the back-end?', CCTM::txtdomain);
  1038. $def['show_ui']['type'] = 'checkbox';
  1039. $def['show_ui']['sort_param'] = 5;
  1040. $def['capability_type']['name'] = 'capability_type';
  1041. $def['capability_type']['label'] = __('Capability Type', CCTM::txtdomain);
  1042. $def['capability_type']['value'] = 'post';
  1043. $def['capability_type']['extra'] = '';
  1044. $def['capability_type']['description'] = __('The post type to use for checking read, edit, and delete capabilities. Default: "post"', CCTM::txtdomain);
  1045. $def['capability_type']['type'] = 'text';
  1046. $def['capability_type']['sort_param'] = 6;
  1047. $def['public']['name'] = 'public';
  1048. $def['public']['label'] = __('Public', CCTM::txtdomain);
  1049. $def['public']['value'] = '1';
  1050. $def['public']['extra'] = '';
  1051. $def['public']['description'] = __('Should these posts be visible on the front-end?', CCTM::txtdomain);
  1052. $def['public']['type'] = 'checkbox';
  1053. $def['public']['sort_param'] = 7;
  1054. $def['hierarchical']['name'] = 'hierarchical';
  1055. $def['hierarchical']['label'] = __('Hierarchical', CCTM::txtdomain);
  1056. $def['hierarchical']['value'] = '';
  1057. $def['hierarchical']['extra'] = '';
  1058. $def['hierarchical']['description'] = __('Allows parent to be specified (Page Attributes should be checked)', CCTM::txtdomain);
  1059. $def['hierarchical']['type'] = 'checkbox';
  1060. $def['hierarchical']['sort_param'] = 8;
  1061. $def['supports_title']['name'] = 'supports[]';
  1062. $def['supports_title']['id'] = 'supports_title';
  1063. $def['supports_title']['label'] = __('Title', CCTM::txtdomain);
  1064. $def['supports_title']['value'] = 'title';
  1065. $def['supports_title']['checked_value'] = 'title';
  1066. $def['supports_title']['extra'] = '';
  1067. $def['supports_title']['description'] = __('Post Title', CCTM::txtdomain);
  1068. $def['supports_title']['type'] = 'checkbox';
  1069. $def['supports_title']['sort_param'] = 20;
  1070. $def['supports_editor']['name'] = 'supports[]';
  1071. $def['supports_editor']['id'] = 'supports_editor';
  1072. $def['supports_editor']['label'] = __('Content', CCTM::txtdomain);
  1073. $def['supports_editor']['value'] = 'editor';
  1074. $def['supports_editor']['checked_value'] = 'editor';
  1075. $def['supports_editor']['extra'] = '';
  1076. $def['supports_editor']['description'] = __('Main content block.', CCTM::txtdomain);
  1077. $def['supports_editor']['type'] = 'checkbox';
  1078. $def['supports_editor']['sort_param'] = 21;
  1079. $def['supports_author']['name'] = 'supports[]';
  1080. $def['supports_author']['id'] = 'supports_author';
  1081. $def['supports_author']['label'] = __('Author', CCTM::txtdomain);
  1082. $def['supports_author']['value'] = '';
  1083. $def['supports_author']['checked_value'] = 'author';
  1084. $def['supports_author']['extra'] = '';
  1085. $def['supports_author']['description'] = __('Track the author.', CCTM::txtdomain);
  1086. $def['supports_author']['type'] = 'checkbox';
  1087. $def['supports_author']['sort_param'] = 22;
  1088. $def['supports_thumbnail']['name'] = 'supports[]';
  1089. $def['supports_thumbnail']['id'] = 'supports_thumbnail';
  1090. $def['supports_thumbnail']['label'] = __('Thumbnail', CCTM::txtdomain);
  1091. $def['supports_thumbnail']['value'] = '';
  1092. $def['supports_thumbnail']['checked_value' ] = 'thumbnail';
  1093. $def['supports_thumbnail']['extra'] = '';
  1094. $def['supports_thumbnail']['description'] = __('Featured image (the activetheme must also support post-thumbnails)', CCTM::txtdomain);
  1095. $def['supports_thumbnail']['type'] = 'checkbox';
  1096. $def['supports_thumbnail']['sort_param'] = 23;
  1097. $def['supports_excerpt']['name'] = 'supports[]';
  1098. $def['supports_excerpt']['id'] = 'supports_excerpt';
  1099. $def['supports_excerpt']['label'] = __('Excerpt', CCTM::txtdomain);
  1100. $def['supports_excerpt']['value'] = '';
  1101. $def['supports_excerpt']['checked_value'] = 'excerpt';
  1102. $def['supports_excerpt']['extra'] = '';
  1103. $def['supports_excerpt']['description'] = __('Small summary field.', CCTM::txtdomain);
  1104. $def['supports_excerpt']['type'] = 'checkbox';
  1105. $def['supports_excerpt']['sort_param'] = 24;
  1106. $def['supports_trackbacks']['name'] = 'supports[]';
  1107. $def['supports_trackbacks']['id'] = 'supports_trackbacks';
  1108. $def['supports_trackbacks']['label'] = __('Trackbacks', CCTM::txtdomain);
  1109. $def['supports_trackbacks']['value'] = '';
  1110. $def['supports_trackbacks']['checked_value'] = 'trackbacks';
  1111. $def['supports_trackbacks']['extra'] = '';
  1112. $def['supports_trackbacks']['description'] = '';
  1113. $def['supports_trackbacks']['type'] = 'checkbox';
  1114. $def['supports_trackbacks']['sort_param'] = 25;
  1115. $def['supports_custom-fields']['name'] = 'supports[]';
  1116. $def['supports_custom-fields']['id'] = 'supports_custom-fields';
  1117. $def['supports_custom-fields']['label'] = __('Supports Custom Fields', CCTM::txtdomain);
  1118. $def['supports_custom-fields']['value'] = '';
  1119. $def['supports_custom-fields']['checked_value'] = 'custom-fields';
  1120. $def['supports_custom-fields']['extra'] = '';
  1121. $def['supports_custom-fields']['description'] = '';
  1122. $def['supports_custom-fields']['type'] = 'checkbox';
  1123. $def['supports_custom-fields']['sort_param'] = 26;
  1124. $def['supports_comments']['name'] = 'supports[]';
  1125. $def['supports_comments']['id'] = 'supports_comments';
  1126. $def['supports_comments']['label'] = __('Enable Comments', CCTM::txtdomain);
  1127. $def['supports_comments']['value'] = '';
  1128. $def['supports_comments']['checked_value'] = 'comments';
  1129. $def['supports_comments']['extra'] = '';
  1130. $def['supports_comments']['description'] = '';
  1131. $def['supports_comments']['type'] = 'checkbox';
  1132. $def['supports_comments']['sort_param'] = 27;
  1133. $def['supports_revisions']['name'] = 'supports[]';
  1134. $def['supports_revisions']['id'] = 'supports_revisions';
  1135. $def['supports_revisions']['label'] = __('Store Revisions', CCTM::txtdomain);
  1136. $def['supports_revisions']['value'] = '';
  1137. $def['supports_revisions']['checked_value'] = 'revisions';
  1138. $def['supports_revisions']['extra'] = '';
  1139. $def['supports_revisions']['description'] = '';
  1140. $def['supports_revisions']['type'] = 'checkbox';
  1141. $def['supports_revisions']['sort_param'] = 28;
  1142. $def['supports_page-attributes']['name'] = 'supports[]';
  1143. $def['supports_page-attributes']['id'] = 'supports_page-attributes';
  1144. $def['supports_page-attributes']['label'] = __('Enable Page Attributes', CCTM::txtdomain);
  1145. $def['supports_page-attributes']['value'] = '';
  1146. $def['supports_page-attributes']['checked_value'] = 'page-attributes';
  1147. $def['supports_page-attributes']['extra'] = '';
  1148. $def['supports_page-attributes']['description'] = __('(template and menu order; hierarchical must be checked)', CCTM::txtdomain);
  1149. $def['supports_page-attributes']['type'] = 'checkbox';
  1150. $def['supports_page-attributes']['sort_param'] = 29;
  1151. $def['menu_position']['name'] = 'menu_position';
  1152. $def['menu_position']['label'] = __('Menu Position', CCTM::txtdomain);
  1153. $def['menu_position']['value'] = '';
  1154. $def['menu_position']['extra'] = '';
  1155. $def['menu_position']['description'] =
  1156. sprintf('%1$s
  1157. <ul style="margin-left:40px;">
  1158. <li><strong>5</strong> - %2$s</li>
  1159. <li><strong>10</strong> - %3$s</li>
  1160. <li><strong>20</strong> - %4$s</li>
  1161. <li><strong>60</strong> - %5$s</li>
  1162. <li><strong>100</strong> - %6$s</li>
  1163. </ul>'
  1164. , __('This setting determines where this post type should appear in the left-hand admin menu. Default: null (below Comments)', CCTM::txtdomain)
  1165. , __('below Posts', CCTM::txtdomain)
  1166. , __('below Media', CCTM::txtdomain)
  1167. , __('below Posts', CCTM::txtdomain)
  1168. , __('below Pages', CCTM::txtdomain)
  1169. , __('below first separator', CCTM::txtdomain)
  1170. , __('below second separator', CCTM::txtdomain)
  1171. );
  1172. $def['menu_position']['type'] = 'text';
  1173. $def['menu_position']['sort_param'] = 30;
  1174. $def['menu_icon']['name'] = 'menu_icon';
  1175. $def['menu_icon']['label'] = __('Menu Icon', CCTM::txtdomain);
  1176. $def['menu_icon']['value'] = '';
  1177. $def['menu_icon']['extra'] = '';
  1178. $def['menu_icon']['description'] = __('Menu icon URL.', CCTM::txtdomain);
  1179. $def['menu_icon']['type'] = 'text';
  1180. $def['menu_icon']['sort_param'] = 31;
  1181. $def['use_default_menu_icon']['name'] = 'use_default_menu_icon';
  1182. $def['use_default_menu_icon']['label'] = __('Use Default Menu Icon', CCTM::txtdomain);
  1183. $def['use_default_menu_icon']['value'] = '1';
  1184. $def['use_default_menu_icon']['extra'] = '';
  1185. $def['use_default_menu_icon']['description'] = __('If checked, your post type will use the posts icon', CCTM::txtdomain);
  1186. $def['use_default_menu_icon']['type'] = 'checkbox';
  1187. $def['use_default_menu_icon']['sort_param'] = 32;
  1188. $def['rewrite_slug']['name'] = 'rewrite_slug';
  1189. $def['rewrite_slug']['label'] = __('Rewrite Slug', CCTM::txtdomain);
  1190. $def['rewrite_slug']['value'] = '';
  1191. $def['rewrite_slug']['extra'] = '';
  1192. $def['rewrite_slug']['description'] = __("Prepend posts with this slug - defaults to post type's name", CCTM::txtdomain);
  1193. $def['rewrite_slug']['type'] = 'text';
  1194. $def['rewrite_slug']['sort_param'] = 35;
  1195. $def['rewrite_with_front']['name'] = 'rewrite_with_front';
  1196. $def['rewrite_with_front']['label'] = __('Rewrite with Permalink Front', CCTM::txtdomain);
  1197. $def['rewrite_with_front']['value'] = '1';
  1198. $def['rewrite_with_front']['extra'] = '';
  1199. $def['rewrite_with_front']['description'] = __("Allow permalinks to be prepended with front base - defaults to checked", CCTM::txtdomain);
  1200. $def['rewrite_with_front']['type'] = 'checkbox';
  1201. $def['rewrite_with_front']['sort_param'] = 35;
  1202. $def['rewrite']['name'] = 'permalink_action';
  1203. $def['rewrite']['label'] = __('Permalink Action', CCTM::txtdomain);
  1204. $def['rewrite']['value'] = 'Off';
  1205. $def['rewrite']['options'] = array('Off','/%postname%/'); // ,'Custom'),
  1206. $def['rewrite']['extra'] = '';
  1207. $def['rewrite']['description'] = sprintf(
  1208. '%1$s
  1209. <ul style="margin-left:20px;">
  1210. <li><strong>Off</strong> - %2$s</li>
  1211. <li><strong>/%postname%/</strong> - %3$s</li>
  1212. <!--li><strong>Custom</strong> - Evaluate the contents of slug</li-->
  1213. <ul>'
  1214. , __('Use permalink rewrites for this post_type? Default: Off', CCTM::txtdomain)
  1215. , __('URLs for custom post_types will always look like: http://site.com/?post_type=book&p=39 even if the rest of the site is using a different permalink structure.', CCTM::txtdomain)
  1216. , __('You MUST use the custom permalink structure: "/%postname%/". Other formats are <strong>not</strong> supported. Your URLs will look like http://site.com/movie/star-wars/', CCTM::txtdomain)
  1217. );
  1218. $def['rewrite']['type'] = 'dropdown';
  1219. $def['rewrite']['sort_param'] = 37;
  1220. $def['query_var']['name'] = 'query_var';
  1221. $def['query_var']['label'] = __('Query Variable', CCTM::txtdomain);
  1222. $def['query_var']['value'] = '';
  1223. $def['query_var']['extra'] = '';
  1224. $def['query_var']['description'] = __('(optional) Name of the query var to use for this post type.
  1225. E.g. "movie" would make for URLs like http://site.com/?movie=star-wars.
  1226. If blank, the default structure is http://site.com/?post_type=movie&p=18', CCTM::txtdomain);
  1227. $def['query_var']['type'] = 'text';
  1228. $def['query_var']['sort_param'] = 38;
  1229. $def['can_export']['name'] = 'can_export';
  1230. $def['can_export']['label'] = __('Can Export', CCTM::txtdomain);
  1231. $def['can_export']['value'] = '1';
  1232. $def['can_export']['extra'] = '';
  1233. $def['can_export']['description'] = __('Can this post_type be exported.', CCTM::txtdomain);
  1234. $def['can_export']['type'] = 'checkbox';
  1235. $def['can_export']['sort_param'] = 40;
  1236. $def['show_in_nav_menus']['name'] = 'show_in_nav_menus';
  1237. $def['show_in_nav_menus']['label'] = __('Show in Nav Menus', CCTM::txtdomain);
  1238. $def['show_in_nav_menus']['value'] = '1';
  1239. $def['show_in_nav_menus']['extra'] = '';
  1240. $def['show_in_nav_menus']['description'] = __('Whether post_type is available for selection in navigation menus. Default: value of public argument', CCTM::txtdomain);
  1241. $def['show_in_nav_menus']['type'] = 'checkbox';
  1242. $def['show_in_nav_menus']['sort_param'] = 40;
  1243. self::$post_type_form_definition = $def;
  1244. }
  1245. /*------------------------------------------------------------------------------
  1246. "Transformation" here refers to the reflexive mapping that is required to create
  1247. a form definition that will generate a form that allows users to define a definition.
  1248. The custom_fields array consists of form element definitions that are used when
  1249. editing or creating a new post. But when we want to *edit* that definition,
  1250. we have to create new form elements that allow us to edit each part of the
  1251. original definition, e.g. we need a text element to allow us to edit
  1252. the "label", we need a textarea element to allow us to edit the "description",
  1253. etc.
  1254. INPUT: $field_def (mixed) a single custom field definition. Something like:
  1255. Array
  1256. (
  1257. [label] => Rating
  1258. [name] => rating
  1259. [description] => MPAA rating
  1260. [type] => dropdown
  1261. [options] => Array
  1262. (
  1263. [0] => PG
  1264. [1] => PG-13
  1265. [2] => R
  1266. )
  1267. [sort_param] =>
  1268. )
  1269. OUTPUT: a modified version of the $custom_field_def_template, with values updated
  1270. based on the incoming $field_def. The 'options' are handled in a special way:
  1271. they are moved to the 'special' key -- this causes the FormGenerator to generate
  1272. text fields for each one so the user can edit the options for their dropdown.
  1273. ------------------------------------------------------------------------------*/
  1274. private static function _transform_data_structure_for_editing($field_def)
  1275. {
  1276. // Collects all 5 translated field definitions for this $field_def
  1277. $translated_defs = array();
  1278. // Copying over all elments from the self::$custom_field_def_template
  1279. foreach ( $field_def as $attr => $val )
  1280. {
  1281. // Is this $attr an editable item for which we must generate a form element?
  1282. if (isset(self::$custom_field_def_template[$attr]) )
  1283. {
  1284. foreach (self::$custom_field_def_template[$attr] as $key => $v )
  1285. {
  1286. $translated_defs[$attr][$key] = self::$custom_field_def_template[$attr][$key];
  1287. }
  1288. }
  1289. // Special handling: 'options' really belong to 'type', e.g. spec. opts for a dropdown
  1290. elseif ( $attr == 'options' && !empty($val) )
  1291. {
  1292. $translated_defs['type']['special'] = $val;
  1293. }
  1294. }
  1295. // Populate the new form definitions with their values from the original
  1296. foreach ( $translated_defs as $field => &$def )
  1297. {
  1298. if ( isset($field_def[$field]))
  1299. {
  1300. $def['value'] = $field_def[$field];
  1301. }
  1302. else
  1303. {
  1304. $def['value'] = '';
  1305. }
  1306. // Associate the group of new elements back to this definition.
  1307. $def['def_i'] = self::$def_i;
  1308. }
  1309. return $translated_defs;
  1310. }
  1311. //! Public Functions
  1312. /*------------------------------------------------------------------------------
  1313. Load CSS and JS for admin folks in the manager. Note that we have to verbosely
  1314. ensure that thickbox css and js are loaded: normally they are tied to the
  1315. "main content" area of the content type, so thickbox would otherwise fail
  1316. if your custom post_type doesn't use the main content type.
  1317. Errors: TO-DO.
  1318. ------------------------------------------------------------------------------*/
  1319. public static function admin_init()
  1320. {
  1321. load_plugin_textdomain( CCTM::txtdomain, '', CCTM_PATH );
  1322. // Set our form defs in this, our makeshift constructor.
  1323. self::_set_post_type_form_definition();
  1324. self::_set_custom_field_def_template();
  1325. // TODO: $E = new WP_Error();
  1326. wp_register_style('CCTM_class'
  1327. , CCTM_URL . '/css/create_or_edit_post_type_class.css');
  1328. wp_register_style('CCTM_gui'
  1329. , CCTM_URL . '/css/create_or_edit_post_type.css');
  1330. wp_enqueue_style('CCTM_class');
  1331. wp_enqueue_style('CCTM_gui');
  1332. // Hand-holding: If your custom post-types omit the main content block,
  1333. // then thickbox will not be queued.
  1334. wp_enqueue_script( 'thickbox' );
  1335. wp_enqueue_style( 'thickbox' );
  1336. }
  1337. /*------------------------------------------------------------------------------
  1338. Adds a link to the settings directly from the plugins page. This filter is
  1339. called for each plugin, so we need to make sure we only alter the links that
  1340. are displayed for THIS plugin.
  1341. INPUT (determined by WordPress):
  1342. $links is a hash of links to display in the format of name => translation e.g.
  1343. array('deactivate' => 'Deactivate')
  1344. $file is the path to plugin's main file (the one with the info header),
  1345. relative to the plugins directory, e.g. 'custom-content-type-manager/index.php'
  1346. OUTPUT: $links array.
  1347. ------------------------------------------------------------------------------*/
  1348. public static function add_plugin_settings_link($links, $file)
  1349. {
  1350. if ( $file == basename(self::get_basepath()) . '/index.php' )
  1351. {
  1352. $settings_link = sprintf('<a href="%s">%s</a>'
  1353. , admin_url( 'options-general.php?page='.self::admin_menu_slug )
  1354. , __('Settings')
  1355. );
  1356. array_unshift( $links, $settings_link );
  1357. }
  1358. return $links;
  1359. }
  1360. // Defines the diretory for this plugin.
  1361. public static function get_basepath(){
  1362. return dirname(dirname(__FILE__));
  1363. }
  1364. /*------------------------------------------------------------------------------
  1365. Create custom post-type menu
  1366. ------------------------------------------------------------------------------*/
  1367. public static function create_admin_menu()
  1368. {
  1369. add_options_page(
  1370. 'Custom Content Types', // page title
  1371. 'Custom Content Types', // menu title
  1372. 'manage_options', // capability
  1373. self::admin_menu_slug, // menu-slug (should be unique)
  1374. 'CCTM::page_main_controller' // callback function
  1375. );
  1376. }
  1377. /*------------------------------------------------------------------------------
  1378. Print errors if they were thrown by the tests. Currently this is triggered as
  1379. an admin notice so as not to disrupt front-end user access, but if there's an
  1380. error, you should fix it! The plugin may behave erratically!
  1381. INPUT: none... ideally I'd pass this a value, but the WP interface doesn't make
  1382. this easy, so instead I just read the class variable: CCTMtests::$errors
  1383. OUTPUT: none directly. But errors are printed if present.
  1384. ------------------------------------------------------------------------------*/
  1385. public static function print_notices()
  1386. {
  1387. if ( !empty(CCTMtests::$errors) )
  1388. {
  1389. $error_items = '';
  1390. foreach ( CCTMtests::$errors as $e )
  1391. {
  1392. $error_items .= "<li>$e</li>";
  1393. }
  1394. $msg = sprintf( __('The %s plugin encountered errors! It cannot load!', CCTM::txtdomain)
  1395. , CCTM::name);
  1396. printf('<div id="custom-post-type-manager-warning" class="error">
  1397. <p>
  1398. <strong>%$1s</strong>
  1399. <ul style="margin-left:30px;">
  1400. %2$s
  1401. </ul>
  1402. </p>
  1403. </div>'
  1404. , $msg
  1405. , $error_items);
  1406. }
  1407. }
  1408. /*------------------------------------------------------------------------------
  1409. Register custom post-types, one by one. Data is stored in the wp_options table
  1410. in a structure that matches exactly what the register_post_type() function
  1411. expectes as arguments.
  1412. See wp-includes/posts.php for examples of how WP registers the default post types
  1413. $def = Array
  1414. (
  1415. 'supports' => Array
  1416. (
  1417. 'title',
  1418. 'editor'
  1419. ),
  1420. 'post_type' => 'book',
  1421. 'singular_label' => 'Book',
  1422. 'label' => 'Books',
  1423. 'description' => 'What I&#039;m reading',
  1424. 'show_ui' => 1,
  1425. 'capability_type' => 'post',
  1426. 'public' => 1,
  1427. 'menu_position' => '10',
  1428. 'menu_icon' => '',
  1429. 'custom_content_type_mgr_create_new_content_type_nonce' => 'd385da6ba3',
  1430. 'Submit' => 'Create New Content Type',
  1431. 'show_in_nav_menus' => '',
  1432. 'can_export' => '',
  1433. 'is_active' => 1,
  1434. );
  1435. FUTURE:
  1436. register_taxonomy( $post_type,
  1437. $cpt_post_types,
  1438. array( 'hierarchical' => get_disp_boolean($cpt_tax_type["hierarchical"]),
  1439. 'label' => $cpt_label,
  1440. 'show_ui' => get_disp_boolean($cpt_tax_type["show_ui"]),
  1441. 'query_var' => get_disp_boolean($cpt_tax_type["query_var"]),
  1442. 'rewrite' => array('slug' => $cpt_rewrite_slug),
  1443. 'singular_label' => $cpt_singular_label,
  1444. 'labels' => $cpt_labels
  1445. ) );
  1446. ------------------------------------------------------------------------------*/
  1447. public static function register_custom_post_types()
  1448. {
  1449. $data = get_option( self::db_key, array() );
  1450. foreach ($data as $post_type => $def)
  1451. {
  1452. if ( isset($def['is_active'])
  1453. && !empty($def['is_active'])
  1454. && !in_array($post_type, self::$built_in_post_types))
  1455. {
  1456. register_post_type( $post_type, $def );
  1457. }
  1458. }
  1459. }
  1460. /*------------------------------------------------------------------------------
  1461. This is the function called when someone clicks on the settings page.
  1462. The job of a controller is to process requests and route them.
  1463. ------------------------------------------------------------------------------*/
  1464. public static function page_main_controller()
  1465. {
  1466. if (!current_user_can('manage_options'))
  1467. {
  1468. wp_die( __('You do not have sufficient permissions to access this page.') );
  1469. }
  1470. $action = (int) self::_get_value($_GET,self::action_param,0);
  1471. $post_type = self::_get_value($_GET,self::post_type_param);
  1472. switch($action)
  1473. {
  1474. case 1: // create new custom post type
  1475. self::_page_create_new_post_type();
  1476. break;
  1477. case 2: // update existing custom post type. Override form def.
  1478. self::$post_type_form_definition['post_type']['type'] = 'readonly';
  1479. self::$post_type_form_definition['post_type']['description'] = __('The name of the post-type cannot be changed. The name may show up in your URLs, e.g. ?movie=star-wars. This will also make a new theme file available, starting with prefix named "single-", e.g. <strong>single-movie.php</strong>.',CCTM::txtdomain);
  1480. self::_page_edit_post_type($post_type);
  1481. break;
  1482. case 3: // delete existing custom post type
  1483. self::_page_delete_post_type($post_type);
  1484. break;
  1485. case 4: // Manage Custom Fields for existing post type
  1486. self::_page_manage_custom_fields($post_type);
  1487. break;
  1488. case 5: // TODO: Manage Taxonomies for existing post type
  1489. break;
  1490. case 6: // Activate post type
  1491. self::_page_activate_post_type($post_type);
  1492. break;
  1493. case 7: // Deactivate post type
  1494. self::_page_deactivate_post_type($post_type);
  1495. break;
  1496. case 8: // Show an example of custom field template
  1497. self::_page_sample_template($post_type);
  1498. break;
  1499. default: // Default: List all post types
  1500. self::_page_show_all_post_types();
  1501. }
  1502. }
  1503. }
  1504. /*EOF*/