├── assets ├── post-columns.css ├── post-edit.css └── post-edit.js ├── changelog.txt ├── fx-updater.php ├── includes ├── admin-scripts.php ├── api │ ├── api.php │ └── templates │ │ ├── list_plugins.php │ │ ├── plugin_information.php │ │ ├── query_plugins.php │ │ └── query_themes.php ├── functions.php ├── get-code │ ├── get-code.php │ └── settings.php ├── library │ └── markdown.php ├── repo-group │ ├── manage-columns.php │ ├── register-taxonomy.php │ └── repo-group.php ├── repo-plugin │ ├── manage-columns.php │ ├── meta-box-data.php │ ├── register-post-type.php │ └── repo-plugin.php ├── repo-theme │ ├── manage-columns.php │ ├── meta-box-data.php │ ├── register-post-type.php │ └── repo-theme.php └── updater.php ├── languages └── fx-updater.pot └── license.txt /assets/post-columns.css: -------------------------------------------------------------------------------- 1 | .up-status-active{ 2 | display: inline-block; 3 | padding: 1px 5px; 4 | background: #d9ffe2; 5 | border: 1px solid #63fd87; 6 | border-radius: 3px; 7 | } 8 | .up-status-inactive{ 9 | display: inline-block; 10 | padding: 1px 5px; 11 | background: #ffd4ae; 12 | border: 1px solid #ffa04b; 13 | border-radius: 3px; 14 | } 15 | .group-status-active{ 16 | display: inline-block; 17 | padding: 0 5px; 18 | border-bottom: 2px solid #63fd87; 19 | } 20 | .group-status-inactive{ 21 | display: inline-block; 22 | padding: 0 5px; 23 | border-bottom: 2px solid #ffa04b; 24 | } 25 | .updater-info strong{ 26 | display: inline-block; 27 | } 28 | .updater-info p{ 29 | padding-left: 30px; 30 | position: relative; 31 | } 32 | .updater-info .dashicons{ 33 | position: absolute; 34 | left: 0; 35 | top: 0; 36 | color: #bbb; 37 | } 38 | .updater-info p:hover .dashicons{ 39 | color: #aaa; 40 | } -------------------------------------------------------------------------------- /assets/post-edit.css: -------------------------------------------------------------------------------- 1 | .fx-upmb-fields:after{ 2 | content:".";display:block;height:0;clear:both;visibility:hidden; 3 | } 4 | .fx-upmb-field{ 5 | padding-bottom: 10px; 6 | margin-bottom: 10x; 7 | border-bottom: 1px solid #eee; 8 | } 9 | .fx-upmb-field:after{ 10 | content:".";display:block;height:0;clear:both;visibility:hidden; 11 | } 12 | .fx-upmb-field:last-child{ 13 | border-bottom: none; 14 | } 15 | .fx-upmb-field-label{ 16 | width: 250px; 17 | float: left; 18 | } 19 | .fx-upmb-field-label label{ 20 | font-size: 16px; 21 | font-weight: 600; 22 | } 23 | .fx-upmb-field-content{ 24 | float: left; 25 | max-width: 100%; 26 | } 27 | 28 | /* Slug */ 29 | #repo_slug{ 30 | width: 250px; 31 | max-width: 100%; 32 | } 33 | /* Theme Folder {ID} */ 34 | #theme_id{ 35 | width: 250px; 36 | max-width: 100%; 37 | } 38 | /* Upload */ 39 | #fxu_download_link{ 40 | width: 400px; 41 | max-width: 100%; 42 | } 43 | /* Version */ 44 | #fxu_version{ 45 | width: 80px; 46 | max-width: 100%; 47 | } 48 | .fx-upmb-desc{ 49 | color: #666; 50 | display: inline-block; 51 | font-style: italic; 52 | font-size: 13px; 53 | } 54 | 55 | /* === PLUGIN === */ 56 | 57 | /* Plugin ID */ 58 | #plugin_id{ 59 | width: 400px; 60 | max-width: 100%; 61 | } 62 | /* Month */ 63 | .fx-upmb-month{ 64 | width: 80px; 65 | padding: 1px 1px 1px 1px; 66 | } 67 | /* Day */ 68 | .fx-upmb-day{ 69 | position: relative; 70 | top: 2px; 71 | padding: 3px 6px 4px 6px; 72 | } 73 | /* Year */ 74 | .fx-upmb-year{ 75 | position: relative; 76 | top: 2px; 77 | padding: 3px 6px 4px 6px; 78 | } 79 | /* WP Version */ 80 | #wp_requires, 81 | #wp_tested{ 82 | width: 80px; 83 | max-width: 100%; 84 | } 85 | /* Changelog */ 86 | #fxu_changelog{ 87 | width: 400px; 88 | max-width: 100%; 89 | } 90 | .fx-upmb-changelog-area{ 91 | font-size: 13px; 92 | line-height: 1.5; 93 | margin: 1em 0; 94 | } 95 | /* Upgrade Notice */ 96 | #upgrade_notice{ 97 | width: 400px; 98 | max-width: 100%; 99 | } 100 | 101 | /* Media Queries */ 102 | @media screen and ( max-width: 1200px ) { 103 | .fx-upmb-field-label{ 104 | width: 100%; 105 | float: none; 106 | } 107 | .fx-upmb-field-content{ 108 | width: 100%; 109 | float: none; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /assets/post-edit.js: -------------------------------------------------------------------------------- 1 | jQuery(document).ready(function($){ 2 | 3 | /* the vars */ 4 | var file_frame; 5 | 6 | /* Click the upload button */ 7 | $( document.body ).on( 'click', '.upload-zip', function(e){ 8 | e.preventDefault(); 9 | 10 | /* Open the frame if already loaded. */ 11 | if ( file_frame ) { 12 | file_frame.open(); 13 | return; 14 | } 15 | 16 | var this_button = $( this ); 17 | 18 | /* If media frame doesn't exist, create it with some options. */ 19 | file_frame = wp.media.frames.file_frame = wp.media({ 20 | className: 'media-frame fx-media-frame', 21 | frame: 'select', 22 | title: fx_upmb_upload.title, 23 | library: { type: 'application/zip' }, 24 | button: { text: fx_upmb_upload.button }, 25 | multiple: false, 26 | }); 27 | 28 | /* insert */ 29 | file_frame.on( 'select', function(){ 30 | 31 | /* Insert */ 32 | var this_attachment = file_frame.state().get('selection').first().toJSON(); 33 | this_button.parents( '.fx-upmb-upload' ).find( '.fx-upmb-upload-url' ).val( this_attachment.url ); 34 | 35 | /* Enable remove button */ 36 | this_button.siblings( '.remove-zip' ).removeClass( 'disabled' ); 37 | 38 | }); 39 | 40 | // Now that everything has been set, let's open up the frame. 41 | file_frame.open(); 42 | }); 43 | 44 | /* === Disabled Button === */ 45 | $( '.remove-zip' ).each( function(i){ 46 | var url_input = $( this ).parents( '.fx-upmb-upload' ).find( '.fx-upmb-upload-url' ).val(); 47 | if( '' == url_input ){ 48 | $( this ).addClass( 'disabled' ); 49 | } 50 | else{ 51 | $( this ).removeClass( 'disabled' ); 52 | } 53 | }); 54 | $( ".fx-upmb-upload-url" ).change( function(){ 55 | var url_input = $( this ).val(); 56 | var remove_zip = $( this ).parents( '.fx-upmb-upload' ).find( '.remove-zip' ); 57 | if( '' == url_input ){ 58 | remove_zip.addClass( 'disabled' ); 59 | } 60 | else{ 61 | remove_zip.removeClass( 'disabled' ); 62 | } 63 | }); 64 | 65 | /* === Remove File === */ 66 | $( document.body ).on( 'click', '.remove-zip', function(e){ 67 | e.preventDefault(); 68 | 69 | /* Remove url input */ 70 | $( this ).parents( '.fx-upmb-upload' ).find( '.fx-upmb-upload-url' ).val(''); 71 | 72 | /* Disabled */ 73 | $( this ).addClass( 'disabled' ); 74 | }); 75 | 76 | }); 77 | -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | 1.0.1 (20 June 2016) 2 | - move menu after dashboard. (menu order:2) 3 | - fix typo: remove_cap on uninstall hook. -------------------------------------------------------------------------------- /fx-updater.php: -------------------------------------------------------------------------------- 1 | 15 | * @copyright Copyright (c) 2016, Genbu Media 16 | **/ 17 | 18 | /* Do not access this file directly */ 19 | if ( ! defined( 'WPINC' ) ) { die; } 20 | 21 | /* Constants 22 | ------------------------------------------ */ 23 | 24 | /* Set plugin version constant. */ 25 | define( 'FX_UPDATER_VERSION', '1.0.1' ); 26 | 27 | /* Set constant path to the plugin directory. */ 28 | define( 'FX_UPDATER_PATH', trailingslashit( plugin_dir_path(__FILE__) ) ); 29 | 30 | /* Set the constant path to the plugin directory URI. */ 31 | define( 'FX_UPDATER_URI', trailingslashit( plugin_dir_url( __FILE__ ) ) ); 32 | 33 | 34 | /* Includes 35 | ------------------------------------------ */ 36 | 37 | /* Load Utility Functions */ 38 | require_once( FX_UPDATER_PATH . 'includes/functions.php' ); 39 | 40 | /* Load Settings Functions */ 41 | require_once( FX_UPDATER_PATH . 'includes/admin-scripts.php' ); 42 | 43 | /* Load Query Functions */ 44 | require_once( FX_UPDATER_PATH . 'includes/api/api.php' ); 45 | 46 | /* Load Settings Functions */ 47 | require_once( FX_UPDATER_PATH . 'includes/get-code/settings.php' ); 48 | 49 | /* Load Group Repo Functions */ 50 | require_once( FX_UPDATER_PATH . 'includes/repo-group/repo-group.php' ); 51 | 52 | /* Load Plugin Repo Functions */ 53 | require_once( FX_UPDATER_PATH . 'includes/repo-plugin/repo-plugin.php' ); 54 | 55 | /* Load Theme Repo Functions */ 56 | require_once( FX_UPDATER_PATH . 'includes/repo-theme/repo-theme.php' ); 57 | 58 | /* Load Updater */ 59 | require_once( FX_UPDATER_PATH . 'includes/updater.php' ); 60 | 61 | 62 | /* Plugins Loaded 63 | ------------------------------------------ */ 64 | 65 | /* Load plugins file */ 66 | add_action( 'plugins_loaded', 'fx_base_plugins_loaded' ); 67 | 68 | /** 69 | * Load plugins file 70 | * @since 0.1.0 71 | */ 72 | function fx_base_plugins_loaded(){ 73 | 74 | /* Load Text Domain (Language Translation) */ 75 | load_plugin_textdomain( 'fx-updater', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' ); 76 | 77 | /* Plugin Action Link */ 78 | add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), 'fx_updater_plugin_action_links' ); 79 | } 80 | 81 | /** 82 | * Add Action Link For Support 83 | * @since 1.0.0 84 | */ 85 | function fx_updater_plugin_action_links( $links ){ 86 | 87 | /* Get current user info */ 88 | if( function_exists( 'wp_get_current_user' ) ){ 89 | $current_user = wp_get_current_user(); 90 | } 91 | else{ 92 | global $current_user; 93 | get_currentuserinfo(); 94 | } 95 | 96 | /* Build support url */ 97 | $support_url = add_query_arg( 98 | array( 99 | 'about' => urlencode( 'f(x) Updater (v.' . FX_UPDATER_VERSION . ')' ), 100 | 'sp_name' => urlencode( $current_user->display_name ), 101 | 'sp_email' => urlencode( $current_user->user_email ), 102 | 'sp_website' => urlencode( home_url() ), 103 | ), 104 | 'http://genbumedia.com/contact/' 105 | ); 106 | 107 | /* Add support link */ 108 | $links[] = '' . __( 'Get Support', 'fx-base' ) . ''; 109 | 110 | return $links; 111 | } 112 | 113 | 114 | /* Activation and Uninstall 115 | ------------------------------------------ */ 116 | 117 | /* Register activation hook. */ 118 | register_activation_hook( __FILE__, 'fx_updater_plugin_activation' ); 119 | 120 | 121 | /** 122 | * Runs only when the plugin is activated. 123 | * @since 1.0.0 124 | */ 125 | function fx_updater_plugin_activation() { 126 | 127 | /* Get the administrator role. */ 128 | $role = get_role( 'administrator' ); 129 | 130 | /* If the administrator role exists, add required capabilities for the plugin. */ 131 | if ( !empty( $role ) ) { 132 | $role->add_cap( 'manage_fx_updaters' ); 133 | $role->add_cap( 'create_fx_updaters' ); 134 | $role->add_cap( 'edit_fx_updaters' ); 135 | } 136 | 137 | /* Temporary Data (5sec) to Add Activation Notice */ 138 | set_transient( 'fx_updater_activation_notice', '1', 5 ); 139 | 140 | /* uninstall plugin */ 141 | register_uninstall_hook( __FILE__, 'fx_updater_plugin_uninstall' ); 142 | } 143 | 144 | 145 | /** 146 | * Uninstall plugin 147 | * @since 0.1.0 148 | */ 149 | function fx_updater_plugin_uninstall(){ 150 | 151 | /* Get the administrator role. */ 152 | $role = get_role( 'administrator' ); 153 | 154 | /* If the administrator role exists, remove added capabilities for the plugin. */ 155 | if ( !empty( $role ) ) { 156 | $role->remove_cap( 'manage_fx_updaters' ); 157 | $role->remove_cap( 'create_fx_updaters' ); 158 | $role->remove_cap( 'edit_fx_updaters' ); 159 | } 160 | } 161 | 162 | 163 | /* Activation Notice 164 | ------------------------------------------ */ 165 | 166 | /* Add admin notice */ 167 | add_action( 'admin_notices', 'fx_updater_plugin_activation_notice' ); 168 | 169 | /** 170 | * Admin Notice on Activation. 171 | * @since 1.0.0 172 | */ 173 | function fx_updater_plugin_activation_notice(){ 174 | $transient = get_transient( 'fx_updater_activation_notice' ); 175 | if( $transient ){ 176 | ?> 177 |
178 |

179 |
180 | __( 'Upload Theme ZIP', 'fx-updater' ), 38 | 'button' => __( 'Insert ZIP File', 'fx-updater' ), 39 | ) 40 | ); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /includes/api/api.php: -------------------------------------------------------------------------------- 1 | 'plugin_repo', 79 | 'post_status' => 'publish', 80 | ); 81 | if( 'plugin' == $query_type ){ 82 | $args['posts_per_page'] = 1; 83 | $args['meta_key'] = 'id'; 84 | $args['meta_value'] = esc_attr( $plugin ); 85 | } 86 | elseif( 'group' == $query_type ){ 87 | $args['posts_per_page'] = -1; 88 | $args['tax_query'] = array( 89 | array( 90 | 'taxonomy' => 'group_repo', 91 | 'field' => 'slug', 92 | 'terms' => sanitize_title( $group ), 93 | ), 94 | ); 95 | } 96 | 97 | /* WP Query */ 98 | $the_query = new WP_Query( $args ); 99 | if ( $the_query->have_posts() ) { 100 | while ( $the_query->have_posts() ) { 101 | $the_query->the_post(); 102 | /* Get Plugin Data */ 103 | $post_id = get_the_ID(); 104 | $id = get_post_meta( $post_id, 'id', true ); 105 | $version = get_post_meta( $post_id, 'version', true ); 106 | $package = get_post_meta( $post_id, 'download_link', true ); 107 | $tested = get_post_meta( $post_id, 'tested', true ); 108 | $notice = get_post_meta( $post_id, 'upgrade_notice', true ); 109 | if( $version ){ 110 | /* Version compare */ 111 | if( isset( $plugins[$id]['Version'] ) && version_compare( $plugins[$id]['Version'], $version, "<" ) ){ 112 | $data[$id] = array( 113 | 'slug' => dirname( $id ), 114 | 'plugin' => $id, 115 | 'new_version' => $version, 116 | ); 117 | if( $package ){ 118 | $data[$id]['package'] = $package; 119 | } 120 | if( $tested ){ 121 | $data[$id]['tested'] = $tested; 122 | } 123 | if( $notice ){ 124 | $data[$id]['upgrade_notice'] = $notice; 125 | } 126 | } 127 | else{ 128 | $data[$id] = array(); 129 | } 130 | } 131 | } 132 | } 133 | wp_reset_postdata(); 134 | 135 | return apply_filters( 'fx_updater_query_plugins', $data, $request ); 136 | } 137 | 138 | /** 139 | * Query Themes 140 | * @since 1.0.0 141 | */ 142 | function fx_updater_query_themes(){ 143 | 144 | /* Stripslash all */ 145 | $request = stripslashes_deep( $_REQUEST ); 146 | 147 | /* Var */ 148 | $group = isset( $request['group'] ) ? $request['group'] : false; 149 | $theme = isset( $request['theme'] ) ? $request['theme'] : false; 150 | $themes = isset( $request['themes'] ) ? $request['themes'] : array(); 151 | $data = array(); 152 | 153 | /* Query Type */ 154 | $query_type = $group ? 'group' : ( $theme ? 'theme' : false ); 155 | if( !$query_type ){ return $data; } 156 | 157 | /* Query Args */ 158 | $args = array( 159 | 'post_type' => 'theme_repo', 160 | 'post_status' => 'publish', 161 | ); 162 | if( 'theme' == $query_type ){ 163 | $args['posts_per_page'] = 1; 164 | $args['meta_key'] = 'id'; 165 | $args['meta_value'] = esc_attr( $theme ); 166 | } 167 | elseif( 'group' == $query_type ){ 168 | $args['posts_per_page'] = -1; 169 | $args['tax_query'] = array( 170 | array( 171 | 'taxonomy' => 'group_repo', 172 | 'field' => 'slug', 173 | 'terms' => sanitize_title( $group ), 174 | ), 175 | ); 176 | } 177 | 178 | /* WP Query */ 179 | $the_query = new WP_Query( $args ); 180 | if ( $the_query->have_posts() ) { 181 | while ( $the_query->have_posts() ) { 182 | $the_query->the_post(); 183 | /* Get Theme Data */ 184 | $post_id = get_the_ID(); 185 | $id = get_post_meta( $post_id, 'id', true ); 186 | $version = get_post_meta( $post_id, 'version', true ); 187 | $package = get_post_meta( $post_id, 'download_link', true ); 188 | if( $version ){ 189 | /* Version compare */ 190 | if( isset( $themes[$id]['Version'] ) && version_compare( $themes[$id]['Version'], $version, "<" ) ){ 191 | $data[$id] = array( 192 | 'theme' => $id, 193 | 'new_version' => $version, 194 | 'url' => $themes[$id]['ThemeURI'], 195 | ); 196 | if( $package ){ 197 | $data[$id]['package'] = $package; 198 | } 199 | } 200 | else{ 201 | $data[$id] = array(); 202 | } 203 | } 204 | } 205 | } 206 | wp_reset_postdata(); 207 | return apply_filters( 'fx_updater_query_themes', $data, $request ); 208 | } 209 | 210 | 211 | /** 212 | * List plugins in a group. 213 | * @since 1.0.0 214 | */ 215 | function fx_updater_list_plugins(){ 216 | 217 | /* Stripslash all */ 218 | $request = stripslashes_deep( $_REQUEST ); 219 | 220 | /* Var */ 221 | $data = array(); 222 | $group = isset( $request['group'] ) ? $request['group'] : false; 223 | if( !$group ) return $data; 224 | 225 | /* Query Args */ 226 | $args = array( 227 | 'post_type' => 'plugin_repo', 228 | 'post_status' => 'publish', 229 | 'posts_per_page' => -1, 230 | 'tax_query' => array( 231 | array( 232 | 'taxonomy' => 'group_repo', 233 | 'field' => 'slug', 234 | 'terms' => sanitize_title( $group ), 235 | ), 236 | ), 237 | ); 238 | 239 | /* WP Query */ 240 | $the_query = new WP_Query( $args ); 241 | if ( $the_query->have_posts() ) { 242 | while ( $the_query->have_posts() ) { 243 | $the_query->the_post(); 244 | /* Get Plugin Data */ 245 | $post_id = get_the_ID(); 246 | $id = get_post_meta( $post_id, 'id', true ); 247 | $version = get_post_meta( $post_id, 'version', true ); 248 | if( $id && $version ){ 249 | $slug = dirname( $id ); 250 | $data[$slug] = $id; 251 | } 252 | } 253 | } 254 | wp_reset_postdata(); 255 | return apply_filters( 'fx_updater_list_plugins', $data, $request ); 256 | } 257 | 258 | /** 259 | * Plugin Information 260 | * @since 1.0.0 261 | */ 262 | function fx_updater_plugin_information(){ 263 | 264 | /* Stripslash all */ 265 | $request = stripslashes_deep( $_REQUEST ); 266 | $plugin = isset( $request['plugin'] ) ? $request['plugin'] : false; 267 | 268 | /* Data */ 269 | $data = array(); 270 | 271 | /* Bail early */ 272 | if( !$plugin ){ return $data; } 273 | 274 | /* Query Args */ 275 | $args = array( 276 | 'post_type' => 'plugin_repo', 277 | 'posts_per_page' => 1, 278 | 'meta_key' => 'id', 279 | 'meta_value' => esc_attr( $plugin ), 280 | ); 281 | 282 | 283 | /* Query */ 284 | $the_query = new WP_Query( $args ); 285 | 286 | /* Start Loop */ 287 | if ( $the_query->have_posts() ) { 288 | while ( $the_query->have_posts() ) { 289 | $the_query->the_post(); 290 | /* Get Plugin Data */ 291 | $post_id = get_the_ID(); 292 | $id = get_post_meta( $post_id, 'id', true ); 293 | $version = get_post_meta( $post_id, 'version', true ); 294 | $updated = get_post_meta( $post_id, 'last_updated', true ); 295 | $download = get_post_meta( $post_id, 'download_link', true ); 296 | $requires = get_post_meta( $post_id, 'requires', true ); 297 | $tested = get_post_meta( $post_id, 'tested', true ); 298 | $changelog = get_post_meta( $post_id, 'section_changelog', true ); 299 | /* Data */ 300 | $data = array( 301 | 'name' => get_the_title( $post_id ), 302 | 'slug' => dirname( $id ), 303 | 'external' => true, 304 | 'sections' => array( 305 | 'changelog' => fx_updater_section_markdown_to_html( $changelog ), 306 | ), 307 | ); 308 | if( $version ){ 309 | $data['version'] = $version; 310 | } 311 | if( $updated ){ 312 | $data['last_updated'] = $updated; 313 | } 314 | if( $download ){ 315 | $data['download_link'] = $download; 316 | } 317 | if( $requires ){ 318 | $data['requires'] = $requires; 319 | } 320 | if( $tested ){ 321 | $data['tested'] = $tested; 322 | } 323 | } 324 | } 325 | wp_reset_postdata(); 326 | return apply_filters( 'fx_updater_plugin_information', $data, $request ); 327 | } 328 | 329 | -------------------------------------------------------------------------------- /includes/api/templates/list_plugins.php: -------------------------------------------------------------------------------- 1 | date( 'Y' ), 65 | 'month' => date( 'm' ), 66 | 'day' => date( 'd' ), 67 | ); 68 | $date = wp_parse_args( $args, $default ); 69 | $date = array_map( 'esc_attr', $date ); 70 | 71 | if( !checkdate( $date['month'], $date['day'], $date['year'] ) ){ 72 | $year = $default['year']; 73 | $month = $default['month']; 74 | $day = $default['day']; 75 | } 76 | else{ 77 | $year = $date['year']; 78 | $month = $date['month']; 79 | $day = $date['day']; 80 | } 81 | return sanitize_title_with_dashes( "{$year}-{$month}-{$day}" ); 82 | } 83 | 84 | 85 | /** 86 | * Sanitize Section 87 | * @since 0.1.0 88 | */ 89 | function fx_updater_sanitize_plugin_section( $input ){ 90 | 91 | /* allowed tags */ 92 | $plugins_allowedtags = array( 93 | 'a' => array( 'href' => array(), 'title' => array(), 'target' => array() ), 94 | 'abbr' => array( 'title' => array() ), 'acronym' => array( 'title' => array() ), 95 | 'code' => array(), 'pre' => array(), 'em' => array(), 'strong' => array(), 96 | 'div' => array(), 'p' => array(), 'ul' => array(), 'ol' => array(), 'li' => array(), 97 | 'h1' => array(), 'h2' => array(), 'h3' => array(), 'h4' => array(), 'h5' => array(), 'h6' => array(), 98 | 'img' => array( 'src' => array(), 'class' => array(), 'alt' => array() ) 99 | ); 100 | 101 | $output = wp_kses( $input, $plugins_allowedtags ); 102 | return $output; 103 | } 104 | 105 | 106 | /** 107 | * Markdown to HTML 108 | * @since 0.1.0 109 | */ 110 | function fx_updater_section_markdown_to_html( $input ){ 111 | 112 | /* Load Markdown Parser */ 113 | require_once( FX_UPDATER_PATH . 'includes/library/markdown.php' ); 114 | 115 | //return wpautop( fx_updater_sanitize_plugin_section( fx_updater_markdown( $input ) ) ); 116 | return fx_updater_markdown( $input ); 117 | } 118 | -------------------------------------------------------------------------------- /includes/get-code/get-code.php: -------------------------------------------------------------------------------- 1 | 50 |
51 | 52 |

53 | 54 |
55 | 8 | # 9 | # Original Markdown 10 | # Copyright (c) 2004-2006 John Gruber 11 | # 12 | # 13 | 14 | 15 | function fx_updater_markdown( $text ) { 16 | # 17 | # Initialize the parser and return the result of its transform method. 18 | # 19 | # Setup static parser variable. 20 | static $parser; 21 | if (!isset($parser)) { 22 | $parser_class = 'fx_Updater_MarkdownExtra_Parser'; 23 | $parser = new $parser_class; 24 | } 25 | 26 | # Transform text using parser. 27 | return $parser->transform($text); 28 | } 29 | 30 | 31 | # 32 | # Markdown Parser Class 33 | # 34 | 35 | class fx_Updater_Markdown_Parser{ 36 | 37 | ### Configuration Variables ### 38 | 39 | # Change to ">" for HTML output. 40 | var $empty_element_suffix = " />"; 41 | var $tab_width = 4; 42 | 43 | # Change to `true` to disallow markup or entities. 44 | var $no_markup = false; 45 | var $no_entities = false; 46 | 47 | # Predefined urls and titles for reference links and images. 48 | var $predef_urls = array(); 49 | var $predef_titles = array(); 50 | 51 | 52 | ### Parser Implementation ### 53 | 54 | # Regex to match balanced [brackets]. 55 | # Needed to insert a maximum bracked depth while converting to PHP. 56 | var $nested_brackets_depth = 6; 57 | var $nested_brackets_re; 58 | 59 | var $nested_url_parenthesis_depth = 4; 60 | var $nested_url_parenthesis_re; 61 | 62 | # Table of hash values for escaped characters: 63 | var $escape_chars = '\`*_{}[]()>#+-.!'; 64 | var $escape_chars_re; 65 | 66 | 67 | function Markdown_Parser() { 68 | # 69 | # Constructor function. Initialize appropriate member variables. 70 | # 71 | $this->_initDetab(); 72 | $this->prepareItalicsAndBold(); 73 | 74 | $this->nested_brackets_re = 75 | str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth). 76 | str_repeat('\])*', $this->nested_brackets_depth); 77 | 78 | $this->nested_url_parenthesis_re = 79 | str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth). 80 | str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth); 81 | 82 | $this->escape_chars_re = '['.preg_quote($this->escape_chars).']'; 83 | 84 | # Sort document, block, and span gamut in ascendent priority order. 85 | asort($this->document_gamut); 86 | asort($this->block_gamut); 87 | asort($this->span_gamut); 88 | } 89 | 90 | 91 | # Internal hashes used during transformation. 92 | var $urls = array(); 93 | var $titles = array(); 94 | var $html_hashes = array(); 95 | 96 | # Status flag to avoid invalid nesting. 97 | var $in_anchor = false; 98 | 99 | 100 | function setup() { 101 | # 102 | # Called before the transformation process starts to setup parser 103 | # states. 104 | # 105 | # Clear global hashes. 106 | $this->urls = $this->predef_urls; 107 | $this->titles = $this->predef_titles; 108 | $this->html_hashes = array(); 109 | 110 | $this->in_anchor = false; 111 | } 112 | 113 | function teardown() { 114 | # 115 | # Called after the transformation process to clear any variable 116 | # which may be taking up memory unnecessarly. 117 | # 118 | $this->urls = array(); 119 | $this->titles = array(); 120 | $this->html_hashes = array(); 121 | } 122 | 123 | 124 | function transform($text) { 125 | # 126 | # Main function. Performs some preprocessing on the input text 127 | # and pass it through the document gamut. 128 | # 129 | $this->setup(); 130 | 131 | # Remove UTF-8 BOM and marker character in input, if present. 132 | $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text); 133 | 134 | # Standardize line endings: 135 | # DOS to Unix and Mac to Unix 136 | $text = preg_replace('{\r\n?}', "\n", $text); 137 | 138 | # Make sure $text ends with a couple of newlines: 139 | $text .= "\n\n"; 140 | 141 | # Convert all tabs to spaces. 142 | $text = $this->detab($text); 143 | 144 | # Turn block-level HTML blocks into hash entries 145 | $text = $this->hashHTMLBlocks($text); 146 | 147 | # Strip any lines consisting only of spaces and tabs. 148 | # This makes subsequent regexen easier to write, because we can 149 | # match consecutive blank lines with /\n+/ instead of something 150 | # contorted like /[ ]*\n+/ . 151 | $text = preg_replace('/^[ ]+$/m', '', $text); 152 | 153 | # Run document gamut methods. 154 | foreach ($this->document_gamut as $method => $priority) { 155 | $text = $this->$method($text); 156 | } 157 | 158 | $this->teardown(); 159 | 160 | return $text . "\n"; 161 | } 162 | 163 | var $document_gamut = array( 164 | # Strip link definitions, store in hashes. 165 | "stripLinkDefinitions" => 20, 166 | 167 | "runBasicBlockGamut" => 30, 168 | ); 169 | 170 | 171 | function stripLinkDefinitions($text) { 172 | # 173 | # Strips link definitions from text, stores the URLs and titles in 174 | # hash references. 175 | # 176 | $less_than_tab = $this->tab_width - 1; 177 | 178 | # Link defs are in the form: ^[id]: url "optional title" 179 | $text = preg_replace_callback('{ 180 | ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1 181 | [ ]* 182 | \n? # maybe *one* newline 183 | [ ]* 184 | (?: 185 | <(.+?)> # url = $2 186 | | 187 | (\S+?) # url = $3 188 | ) 189 | [ ]* 190 | \n? # maybe one newline 191 | [ ]* 192 | (?: 193 | (?<=\s) # lookbehind for whitespace 194 | ["(] 195 | (.*?) # title = $4 196 | [")] 197 | [ ]* 198 | )? # title is optional 199 | (?:\n+|\Z) 200 | }xm', 201 | array(&$this, '_stripLinkDefinitions_callback'), 202 | $text); 203 | return $text; 204 | } 205 | function _stripLinkDefinitions_callback($matches) { 206 | $link_id = strtolower($matches[1]); 207 | $url = $matches[2] == '' ? $matches[3] : $matches[2]; 208 | $this->urls[$link_id] = $url; 209 | $this->titles[$link_id] =& $matches[4]; 210 | return ''; # String that will replace the block 211 | } 212 | 213 | 214 | function hashHTMLBlocks($text) { 215 | if ($this->no_markup) return $text; 216 | 217 | $less_than_tab = $this->tab_width - 1; 218 | 219 | # Hashify HTML blocks: 220 | # We only want to do this for block-level HTML tags, such as headers, 221 | # lists, and tables. That's because we still want to wrap

s around 222 | # "paragraphs" that are wrapped in non-block-level tags, such as anchors, 223 | # phrase emphasis, and spans. The list of tags we're looking for is 224 | # hard-coded: 225 | # 226 | # * List "a" is made of tags which can be both inline or block-level. 227 | # These will be treated block-level when the start tag is alone on 228 | # its line, otherwise they're not matched here and will be taken as 229 | # inline later. 230 | # * List "b" is made of tags which are always block-level; 231 | # 232 | $block_tags_a_re = 'ins|del'; 233 | $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'. 234 | 'script|noscript|form|fieldset|iframe|math|svg|'. 235 | 'article|section|nav|aside|hgroup|header|footer|'. 236 | 'figure'; 237 | 238 | # Regular expression for the content of a block tag. 239 | $nested_tags_level = 4; 240 | $attr = ' 241 | (?> # optional tag attributes 242 | \s # starts with whitespace 243 | (?> 244 | [^>"/]+ # text outside quotes 245 | | 246 | /+(?!>) # slash not followed by ">" 247 | | 248 | "[^"]*" # text inside double quotes (tolerate ">") 249 | | 250 | \'[^\']*\' # text inside single quotes (tolerate ">") 251 | )* 252 | )? 253 | '; 254 | $content = 255 | str_repeat(' 256 | (?> 257 | [^<]+ # content without tag 258 | | 259 | <\2 # nested opening tag 260 | '.$attr.' # attributes 261 | (?> 262 | /> 263 | | 264 | >', $nested_tags_level). # end of opening tag 265 | '.*?'. # last level nested tag content 266 | str_repeat(' 267 | # closing nested tag 268 | ) 269 | | 270 | <(?!/\2\s*> # other tags with a different name 271 | ) 272 | )*', 273 | $nested_tags_level); 274 | $content2 = str_replace('\2', '\3', $content); 275 | 276 | # First, look for nested blocks, e.g.: 277 | #

278 | #
279 | # tags for inner block must be indented. 280 | #
281 | #
282 | # 283 | # The outermost tags must start at the left margin for this to match, and 284 | # the inner nested divs must be indented. 285 | # We need to do this before the next, more liberal match, because the next 286 | # match will start at the first `
` and stop at the first `
`. 287 | $text = preg_replace_callback('{(?> 288 | (?> 289 | (?<=\n\n) # Starting after a blank line 290 | | # or 291 | \A\n? # the beginning of the doc 292 | ) 293 | ( # save in $1 294 | 295 | # Match from `\n` to `\n`, handling nested tags 296 | # in between. 297 | 298 | [ ]{0,'.$less_than_tab.'} 299 | <('.$block_tags_b_re.')# start tag = $2 300 | '.$attr.'> # attributes followed by > and \n 301 | '.$content.' # content, support nesting 302 | # the matching end tag 303 | [ ]* # trailing spaces/tabs 304 | (?=\n+|\Z) # followed by a newline or end of document 305 | 306 | | # Special version for tags of group a. 307 | 308 | [ ]{0,'.$less_than_tab.'} 309 | <('.$block_tags_a_re.')# start tag = $3 310 | '.$attr.'>[ ]*\n # attributes followed by > 311 | '.$content2.' # content, support nesting 312 | # the matching end tag 313 | [ ]* # trailing spaces/tabs 314 | (?=\n+|\Z) # followed by a newline or end of document 315 | 316 | | # Special case just for
. It was easier to make a special 317 | # case than to make the other regex more complicated. 318 | 319 | [ ]{0,'.$less_than_tab.'} 320 | <(hr) # start tag = $2 321 | '.$attr.' # attributes 322 | /?> # the matching end tag 323 | [ ]* 324 | (?=\n{2,}|\Z) # followed by a blank line or end of document 325 | 326 | | # Special case for standalone HTML comments: 327 | 328 | [ ]{0,'.$less_than_tab.'} 329 | (?s: 330 | 331 | ) 332 | [ ]* 333 | (?=\n{2,}|\Z) # followed by a blank line or end of document 334 | 335 | | # PHP and ASP-style processor instructions ( 342 | ) 343 | [ ]* 344 | (?=\n{2,}|\Z) # followed by a blank line or end of document 345 | 346 | ) 347 | )}Sxmi', 348 | array(&$this, '_hashHTMLBlocks_callback'), 349 | $text); 350 | 351 | return $text; 352 | } 353 | function _hashHTMLBlocks_callback($matches) { 354 | $text = $matches[1]; 355 | $key = $this->hashBlock($text); 356 | return "\n\n$key\n\n"; 357 | } 358 | 359 | 360 | function hashPart($text, $boundary = 'X') { 361 | # 362 | # Called whenever a tag must be hashed when a function insert an atomic 363 | # element in the text stream. Passing $text to through this function gives 364 | # a unique text-token which will be reverted back when calling unhash. 365 | # 366 | # The $boundary argument specify what character should be used to surround 367 | # the token. By convension, "B" is used for block elements that needs not 368 | # to be wrapped into paragraph tags at the end, ":" is used for elements 369 | # that are word separators and "X" is used in the general case. 370 | # 371 | # Swap back any tag hash found in $text so we do not have to `unhash` 372 | # multiple times at the end. 373 | $text = $this->unhash($text); 374 | 375 | # Then hash the block. 376 | static $i = 0; 377 | $key = "$boundary\x1A" . ++$i . $boundary; 378 | $this->html_hashes[$key] = $text; 379 | return $key; # String that will replace the tag. 380 | } 381 | 382 | 383 | function hashBlock($text) { 384 | # 385 | # Shortcut function for hashPart with block-level boundaries. 386 | # 387 | return $this->hashPart($text, 'B'); 388 | } 389 | 390 | 391 | var $block_gamut = array( 392 | # 393 | # These are all the transformations that form block-level 394 | # tags like paragraphs, headers, and list items. 395 | # 396 | "doHeaders" => 10, 397 | "doHorizontalRules" => 20, 398 | 399 | "doLists" => 40, 400 | "doCodeBlocks" => 50, 401 | "doBlockQuotes" => 60, 402 | ); 403 | 404 | function runBlockGamut($text) { 405 | # 406 | # Run block gamut tranformations. 407 | # 408 | # We need to escape raw HTML in Markdown source before doing anything 409 | # else. This need to be done for each block, and not only at the 410 | # begining in the Markdown function since hashed blocks can be part of 411 | # list items and could have been indented. Indented blocks would have 412 | # been seen as a code block in a previous pass of hashHTMLBlocks. 413 | $text = $this->hashHTMLBlocks($text); 414 | 415 | return $this->runBasicBlockGamut($text); 416 | } 417 | 418 | function runBasicBlockGamut($text) { 419 | # 420 | # Run block gamut tranformations, without hashing HTML blocks. This is 421 | # useful when HTML blocks are known to be already hashed, like in the first 422 | # whole-document pass. 423 | # 424 | foreach ($this->block_gamut as $method => $priority) { 425 | $text = $this->$method($text); 426 | } 427 | 428 | # Finally form paragraph and restore hashed blocks. 429 | $text = $this->formParagraphs($text); 430 | 431 | return $text; 432 | } 433 | 434 | 435 | function doHorizontalRules($text) { 436 | # Do Horizontal Rules: 437 | return preg_replace( 438 | '{ 439 | ^[ ]{0,3} # Leading space 440 | ([-*_]) # $1: First marker 441 | (?> # Repeated marker group 442 | [ ]{0,2} # Zero, one, or two spaces. 443 | \1 # Marker character 444 | ){2,} # Group repeated at least twice 445 | [ ]* # Tailing spaces 446 | $ # End of line. 447 | }mx', 448 | "\n".$this->hashBlock("empty_element_suffix")."\n", 449 | $text); 450 | } 451 | 452 | 453 | var $span_gamut = array( 454 | # 455 | # These are all the transformations that occur *within* block-level 456 | # tags like paragraphs, headers, and list items. 457 | # 458 | # Process character escapes, code spans, and inline HTML 459 | # in one shot. 460 | "parseSpan" => -30, 461 | 462 | # Process anchor and image tags. Images must come first, 463 | # because ![foo][f] looks like an anchor. 464 | "doImages" => 10, 465 | "doAnchors" => 20, 466 | 467 | # Make links out of things like `` 468 | # Must come after doAnchors, because you can use < and > 469 | # delimiters in inline links like [this](). 470 | "doAutoLinks" => 30, 471 | "encodeAmpsAndAngles" => 40, 472 | 473 | "doItalicsAndBold" => 50, 474 | "doHardBreaks" => 60, 475 | ); 476 | 477 | function runSpanGamut($text) { 478 | # 479 | # Run span gamut tranformations. 480 | # 481 | foreach ($this->span_gamut as $method => $priority) { 482 | $text = $this->$method($text); 483 | } 484 | 485 | return $text; 486 | } 487 | 488 | 489 | function doHardBreaks($text) { 490 | # Do hard breaks: 491 | return preg_replace_callback('/ {2,}\n/', 492 | array(&$this, '_doHardBreaks_callback'), $text); 493 | } 494 | function _doHardBreaks_callback($matches) { 495 | return $this->hashPart("empty_element_suffix\n"); 496 | } 497 | 498 | 499 | function doAnchors($text) { 500 | # 501 | # Turn Markdown link shortcuts into XHTML tags. 502 | # 503 | if ($this->in_anchor) return $text; 504 | $this->in_anchor = true; 505 | 506 | # 507 | # First, handle reference-style links: [link text] [id] 508 | # 509 | $text = preg_replace_callback('{ 510 | ( # wrap whole match in $1 511 | \[ 512 | ('.$this->nested_brackets_re.') # link text = $2 513 | \] 514 | 515 | [ ]? # one optional space 516 | (?:\n[ ]*)? # one optional newline followed by spaces 517 | 518 | \[ 519 | (.*?) # id = $3 520 | \] 521 | ) 522 | }xs', 523 | array(&$this, '_doAnchors_reference_callback'), $text); 524 | 525 | # 526 | # Next, inline-style links: [link text](url "optional title") 527 | # 528 | $text = preg_replace_callback('{ 529 | ( # wrap whole match in $1 530 | \[ 531 | ('.$this->nested_brackets_re.') # link text = $2 532 | \] 533 | \( # literal paren 534 | [ \n]* 535 | (?: 536 | <(.+?)> # href = $3 537 | | 538 | ('.$this->nested_url_parenthesis_re.') # href = $4 539 | ) 540 | [ \n]* 541 | ( # $5 542 | ([\'"]) # quote char = $6 543 | (.*?) # Title = $7 544 | \6 # matching quote 545 | [ \n]* # ignore any spaces/tabs between closing quote and ) 546 | )? # title is optional 547 | \) 548 | ) 549 | }xs', 550 | array(&$this, '_doAnchors_inline_callback'), $text); 551 | 552 | # 553 | # Last, handle reference-style shortcuts: [link text] 554 | # These must come last in case you've also got [link text][1] 555 | # or [link text](/foo) 556 | # 557 | $text = preg_replace_callback('{ 558 | ( # wrap whole match in $1 559 | \[ 560 | ([^\[\]]+) # link text = $2; can\'t contain [ or ] 561 | \] 562 | ) 563 | }xs', 564 | array(&$this, '_doAnchors_reference_callback'), $text); 565 | 566 | $this->in_anchor = false; 567 | return $text; 568 | } 569 | function _doAnchors_reference_callback($matches) { 570 | $whole_match = $matches[1]; 571 | $link_text = $matches[2]; 572 | $link_id =& $matches[3]; 573 | 574 | if ($link_id == "") { 575 | # for shortcut links like [this][] or [this]. 576 | $link_id = $link_text; 577 | } 578 | 579 | # lower-case and turn embedded newlines into spaces 580 | $link_id = strtolower($link_id); 581 | $link_id = preg_replace('{[ ]?\n}', ' ', $link_id); 582 | 583 | if (isset($this->urls[$link_id])) { 584 | $url = $this->urls[$link_id]; 585 | $url = $this->encodeAttribute($url); 586 | 587 | $result = "titles[$link_id] ) ) { 589 | $title = $this->titles[$link_id]; 590 | $title = $this->encodeAttribute($title); 591 | $result .= " title=\"$title\""; 592 | } 593 | 594 | $link_text = $this->runSpanGamut($link_text); 595 | $result .= ">$link_text"; 596 | $result = $this->hashPart($result); 597 | } 598 | else { 599 | $result = $whole_match; 600 | } 601 | return $result; 602 | } 603 | function _doAnchors_inline_callback($matches) { 604 | $whole_match = $matches[1]; 605 | $link_text = $this->runSpanGamut($matches[2]); 606 | $url = $matches[3] == '' ? $matches[4] : $matches[3]; 607 | $title =& $matches[7]; 608 | 609 | $url = $this->encodeAttribute($url); 610 | 611 | $result = "encodeAttribute($title); 614 | $result .= " title=\"$title\""; 615 | } 616 | 617 | $link_text = $this->runSpanGamut($link_text); 618 | $result .= ">$link_text"; 619 | 620 | return $this->hashPart($result); 621 | } 622 | 623 | 624 | function doImages($text) { 625 | # 626 | # Turn Markdown image shortcuts into tags. 627 | # 628 | # 629 | # First, handle reference-style labeled images: ![alt text][id] 630 | # 631 | $text = preg_replace_callback('{ 632 | ( # wrap whole match in $1 633 | !\[ 634 | ('.$this->nested_brackets_re.') # alt text = $2 635 | \] 636 | 637 | [ ]? # one optional space 638 | (?:\n[ ]*)? # one optional newline followed by spaces 639 | 640 | \[ 641 | (.*?) # id = $3 642 | \] 643 | 644 | ) 645 | }xs', 646 | array(&$this, '_doImages_reference_callback'), $text); 647 | 648 | # 649 | # Next, handle inline images: ![alt text](url "optional title") 650 | # Don't forget: encode * and _ 651 | # 652 | $text = preg_replace_callback('{ 653 | ( # wrap whole match in $1 654 | !\[ 655 | ('.$this->nested_brackets_re.') # alt text = $2 656 | \] 657 | \s? # One optional whitespace character 658 | \( # literal paren 659 | [ \n]* 660 | (?: 661 | <(\S*)> # src url = $3 662 | | 663 | ('.$this->nested_url_parenthesis_re.') # src url = $4 664 | ) 665 | [ \n]* 666 | ( # $5 667 | ([\'"]) # quote char = $6 668 | (.*?) # title = $7 669 | \6 # matching quote 670 | [ \n]* 671 | )? # title is optional 672 | \) 673 | ) 674 | }xs', 675 | array(&$this, '_doImages_inline_callback'), $text); 676 | 677 | return $text; 678 | } 679 | function _doImages_reference_callback($matches) { 680 | $whole_match = $matches[1]; 681 | $alt_text = $matches[2]; 682 | $link_id = strtolower($matches[3]); 683 | 684 | if ($link_id == "") { 685 | $link_id = strtolower($alt_text); # for shortcut links like ![this][]. 686 | } 687 | 688 | $alt_text = $this->encodeAttribute($alt_text); 689 | if (isset($this->urls[$link_id])) { 690 | $url = $this->encodeAttribute($this->urls[$link_id]); 691 | $result = "\"$alt_text\"";titles[$link_id])) { 693 | $title = $this->titles[$link_id]; 694 | $title = $this->encodeAttribute($title); 695 | $result .= " title=\"$title\""; 696 | } 697 | $result .= $this->empty_element_suffix; 698 | $result = $this->hashPart($result); 699 | } 700 | else { 701 | # If there's no such link ID, leave intact: 702 | $result = $whole_match; 703 | } 704 | 705 | return $result; 706 | } 707 | function _doImages_inline_callback($matches) { 708 | $whole_match = $matches[1]; 709 | $alt_text = $matches[2]; 710 | $url = $matches[3] == '' ? $matches[4] : $matches[3]; 711 | $title =& $matches[7]; 712 | 713 | $alt_text = $this->encodeAttribute($alt_text); 714 | $url = $this->encodeAttribute($url); 715 | $result = "\"$alt_text\"";encodeAttribute($title); 718 | $result .= " title=\"$title\""; # $title already quoted 719 | } 720 | $result .= $this->empty_element_suffix; 721 | 722 | return $this->hashPart($result); 723 | } 724 | 725 | 726 | function doHeaders($text) { 727 | # Setext-style headers: 728 | # Header 1 729 | # ======== 730 | # 731 | # Header 2 732 | # -------- 733 | # 734 | $text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx', 735 | array(&$this, '_doHeaders_callback_setext'), $text); 736 | 737 | # atx-style headers: 738 | # # Header 1 739 | # ## Header 2 740 | # ## Header 2 with closing hashes ## 741 | # ... 742 | # ###### Header 6 743 | # 744 | $text = preg_replace_callback('{ 745 | ^(\#{1,6}) # $1 = string of #\'s 746 | [ ]* 747 | (.+?) # $2 = Header text 748 | [ ]* 749 | \#* # optional closing #\'s (not counted) 750 | \n+ 751 | }xm', 752 | array(&$this, '_doHeaders_callback_atx'), $text); 753 | 754 | return $text; 755 | } 756 | function _doHeaders_callback_setext($matches) { 757 | # Terrible hack to check we haven't found an empty list item. 758 | if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1])) 759 | return $matches[0]; 760 | 761 | $level = $matches[2]{0} == '=' ? 1 : 2; 762 | $block = "".$this->runSpanGamut($matches[1]).""; 763 | return "\n" . $this->hashBlock($block) . "\n\n"; 764 | } 765 | function _doHeaders_callback_atx($matches) { 766 | $level = strlen($matches[1]); 767 | $block = "".$this->runSpanGamut($matches[2]).""; 768 | return "\n" . $this->hashBlock($block) . "\n\n"; 769 | } 770 | 771 | 772 | function doLists($text) { 773 | # 774 | # Form HTML ordered (numbered) and unordered (bulleted) lists. 775 | # 776 | $less_than_tab = $this->tab_width - 1; 777 | 778 | # Re-usable patterns to match list item bullets and number markers: 779 | $marker_ul_re = '[*+-]'; 780 | $marker_ol_re = '\d+[\.]'; 781 | $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)"; 782 | 783 | $markers_relist = array( 784 | $marker_ul_re => $marker_ol_re, 785 | $marker_ol_re => $marker_ul_re, 786 | ); 787 | 788 | foreach ($markers_relist as $marker_re => $other_marker_re) { 789 | # Re-usable pattern to match any entirel ul or ol list: 790 | $whole_list_re = ' 791 | ( # $1 = whole list 792 | ( # $2 793 | ([ ]{0,'.$less_than_tab.'}) # $3 = number of spaces 794 | ('.$marker_re.') # $4 = first list item marker 795 | [ ]+ 796 | ) 797 | (?s:.+?) 798 | ( # $5 799 | \z 800 | | 801 | \n{2,} 802 | (?=\S) 803 | (?! # Negative lookahead for another list item marker 804 | [ ]* 805 | '.$marker_re.'[ ]+ 806 | ) 807 | | 808 | (?= # Lookahead for another kind of list 809 | \n 810 | \3 # Must have the same indentation 811 | '.$other_marker_re.'[ ]+ 812 | ) 813 | ) 814 | ) 815 | '; // mx 816 | 817 | # We use a different prefix before nested lists than top-level lists. 818 | # See extended comment in _ProcessListItems(). 819 | 820 | if ($this->list_level) { 821 | $text = preg_replace_callback('{ 822 | ^ 823 | '.$whole_list_re.' 824 | }mx', 825 | array(&$this, '_doLists_callback'), $text); 826 | } 827 | else { 828 | $text = preg_replace_callback('{ 829 | (?:(?<=\n)\n|\A\n?) # Must eat the newline 830 | '.$whole_list_re.' 831 | }mx', 832 | array(&$this, '_doLists_callback'), $text); 833 | } 834 | } 835 | 836 | return $text; 837 | } 838 | function _doLists_callback($matches) { 839 | # Re-usable patterns to match list item bullets and number markers: 840 | $marker_ul_re = '[*+-]'; 841 | $marker_ol_re = '\d+[\.]'; 842 | $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)"; 843 | 844 | $list = $matches[1]; 845 | $list_type = preg_match("/$marker_ul_re/", $matches[4]) ? "ul" : "ol"; 846 | 847 | $marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re ); 848 | 849 | $list .= "\n"; 850 | $result = $this->processListItems($list, $marker_any_re); 851 | 852 | $result = $this->hashBlock("<$list_type>\n" . $result . ""); 853 | return "\n". $result ."\n\n"; 854 | } 855 | 856 | var $list_level = 0; 857 | 858 | function processListItems($list_str, $marker_any_re) { 859 | # 860 | # Process the contents of a single ordered or unordered list, splitting it 861 | # into individual list items. 862 | # 863 | # The $this->list_level global keeps track of when we're inside a list. 864 | # Each time we enter a list, we increment it; when we leave a list, 865 | # we decrement. If it's zero, we're not in a list anymore. 866 | # 867 | # We do this because when we're not inside a list, we want to treat 868 | # something like this: 869 | # 870 | # I recommend upgrading to version 871 | # 8. Oops, now this line is treated 872 | # as a sub-list. 873 | # 874 | # As a single paragraph, despite the fact that the second line starts 875 | # with a digit-period-space sequence. 876 | # 877 | # Whereas when we're inside a list (or sub-list), that line will be 878 | # treated as the start of a sub-list. What a kludge, huh? This is 879 | # an aspect of Markdown's syntax that's hard to parse perfectly 880 | # without resorting to mind-reading. Perhaps the solution is to 881 | # change the syntax rules such that sub-lists must start with a 882 | # starting cardinal number; e.g. "1." or "a.". 883 | 884 | $this->list_level++; 885 | 886 | # trim trailing blank lines: 887 | $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str); 888 | 889 | $list_str = preg_replace_callback('{ 890 | (\n)? # leading line = $1 891 | (^[ ]*) # leading whitespace = $2 892 | ('.$marker_any_re.' # list marker and space = $3 893 | (?:[ ]+|(?=\n)) # space only required if item is not empty 894 | ) 895 | ((?s:.*?)) # list item text = $4 896 | (?:(\n+(?=\n))|\n) # tailing blank line = $5 897 | (?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n)))) 898 | }xm', 899 | array(&$this, '_processListItems_callback'), $list_str); 900 | 901 | $this->list_level--; 902 | return $list_str; 903 | } 904 | function _processListItems_callback($matches) { 905 | $item = $matches[4]; 906 | $leading_line =& $matches[1]; 907 | $leading_space =& $matches[2]; 908 | $marker_space = $matches[3]; 909 | $tailing_blank_line =& $matches[5]; 910 | 911 | if ($leading_line || $tailing_blank_line || 912 | preg_match('/\n{2,}/', $item)) 913 | { 914 | # Replace marker with the appropriate whitespace indentation 915 | $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item; 916 | $item = $this->runBlockGamut($this->outdent($item)."\n"); 917 | } 918 | else { 919 | # Recursion for sub-lists: 920 | $item = $this->doLists($this->outdent($item)); 921 | $item = preg_replace('/\n+$/', '', $item); 922 | $item = $this->runSpanGamut($item); 923 | } 924 | 925 | return "
  • " . $item . "
  • \n"; 926 | } 927 | 928 | 929 | function doCodeBlocks($text) { 930 | # 931 | # Process Markdown `
    ` blocks.
     932 | 	#
     933 | 		$text = preg_replace_callback('{
     934 | 				(?:\n\n|\A\n?)
     935 | 				(	            # $1 = the code block -- one or more lines, starting with a space/tab
     936 | 				  (?>
     937 | 					[ ]{'.$this->tab_width.'}  # Lines must start with a tab or a tab-width of spaces
     938 | 					.*\n+
     939 | 				  )+
     940 | 				)
     941 | 				((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
     942 | 			}xm',
     943 | 			array(&$this, '_doCodeBlocks_callback'), $text);
     944 | 
     945 | 		return $text;
     946 | 	}
     947 | 	function _doCodeBlocks_callback($matches) {
     948 | 		$codeblock = $matches[1];
     949 | 
     950 | 		$codeblock = $this->outdent($codeblock);
     951 | 		$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
     952 | 
     953 | 		# trim leading newlines and trailing newlines
     954 | 		$codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
     955 | 
     956 | 		$codeblock = "
    $codeblock\n
    "; 957 | return "\n\n".$this->hashBlock($codeblock)."\n\n"; 958 | } 959 | 960 | 961 | function makeCodeSpan($code) { 962 | # 963 | # Create a code span markup for $code. Called from handleSpanToken. 964 | # 965 | $code = htmlspecialchars(trim($code), ENT_NOQUOTES); 966 | return $this->hashPart("$code"); 967 | } 968 | 969 | 970 | var $em_relist = array( 971 | '' => '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? '(?:(? '(?<=\S|^)(? '(?<=\S|^)(?em_relist as $em => $em_re) { 993 | foreach ($this->strong_relist as $strong => $strong_re) { 994 | # Construct list of allowed token expressions. 995 | $token_relist = array(); 996 | if (isset($this->em_strong_relist["$em$strong"])) { 997 | $token_relist[] = $this->em_strong_relist["$em$strong"]; 998 | } 999 | $token_relist[] = $em_re; 1000 | $token_relist[] = $strong_re; 1001 | 1002 | # Construct master expression from list. 1003 | $token_re = '{('. implode('|', $token_relist) .')}'; 1004 | $this->em_strong_prepared_relist["$em$strong"] = $token_re; 1005 | } 1006 | } 1007 | } 1008 | 1009 | function doItalicsAndBold($text) { 1010 | $token_stack = array(''); 1011 | $text_stack = array(''); 1012 | $em = ''; 1013 | $strong = ''; 1014 | $tree_char_em = false; 1015 | 1016 | while (1) { 1017 | # 1018 | # Get prepared regular expression for seraching emphasis tokens 1019 | # in current context. 1020 | # 1021 | $token_re = $this->em_strong_prepared_relist["$em$strong"]; 1022 | 1023 | # 1024 | # Each loop iteration search for the next emphasis token. 1025 | # Each token is then passed to handleSpanToken. 1026 | # 1027 | $parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE); 1028 | $text_stack[0] .= $parts[0]; 1029 | $token =& $parts[1]; 1030 | $text =& $parts[2]; 1031 | 1032 | if (empty($token)) { 1033 | # Reached end of text span: empty stack without emitting. 1034 | # any more emphasis. 1035 | while ($token_stack[0]) { 1036 | $text_stack[1] .= array_shift($token_stack); 1037 | $text_stack[0] .= array_shift($text_stack); 1038 | } 1039 | break; 1040 | } 1041 | 1042 | $token_len = strlen($token); 1043 | if ($tree_char_em) { 1044 | # Reached closing marker while inside a three-char emphasis. 1045 | if ($token_len == 3) { 1046 | # Three-char closing marker, close em and strong. 1047 | array_shift($token_stack); 1048 | $span = array_shift($text_stack); 1049 | $span = $this->runSpanGamut($span); 1050 | $span = "$span"; 1051 | $text_stack[0] .= $this->hashPart($span); 1052 | $em = ''; 1053 | $strong = ''; 1054 | } else { 1055 | # Other closing marker: close one em or strong and 1056 | # change current token state to match the other 1057 | $token_stack[0] = str_repeat($token{0}, 3-$token_len); 1058 | $tag = $token_len == 2 ? "strong" : "em"; 1059 | $span = $text_stack[0]; 1060 | $span = $this->runSpanGamut($span); 1061 | $span = "<$tag>$span"; 1062 | $text_stack[0] = $this->hashPart($span); 1063 | $$tag = ''; # $$tag stands for $em or $strong 1064 | } 1065 | $tree_char_em = false; 1066 | } else if ($token_len == 3) { 1067 | if ($em) { 1068 | # Reached closing marker for both em and strong. 1069 | # Closing strong marker: 1070 | for ($i = 0; $i < 2; ++$i) { 1071 | $shifted_token = array_shift($token_stack); 1072 | $tag = strlen($shifted_token) == 2 ? "strong" : "em"; 1073 | $span = array_shift($text_stack); 1074 | $span = $this->runSpanGamut($span); 1075 | $span = "<$tag>$span"; 1076 | $text_stack[0] .= $this->hashPart($span); 1077 | $$tag = ''; # $$tag stands for $em or $strong 1078 | } 1079 | } else { 1080 | # Reached opening three-char emphasis marker. Push on token 1081 | # stack; will be handled by the special condition above. 1082 | $em = $token{0}; 1083 | $strong = "$em$em"; 1084 | array_unshift($token_stack, $token); 1085 | array_unshift($text_stack, ''); 1086 | $tree_char_em = true; 1087 | } 1088 | } else if ($token_len == 2) { 1089 | if ($strong) { 1090 | # Unwind any dangling emphasis marker: 1091 | if (strlen($token_stack[0]) == 1) { 1092 | $text_stack[1] .= array_shift($token_stack); 1093 | $text_stack[0] .= array_shift($text_stack); 1094 | } 1095 | # Closing strong marker: 1096 | array_shift($token_stack); 1097 | $span = array_shift($text_stack); 1098 | $span = $this->runSpanGamut($span); 1099 | $span = "$span"; 1100 | $text_stack[0] .= $this->hashPart($span); 1101 | $strong = ''; 1102 | } else { 1103 | array_unshift($token_stack, $token); 1104 | array_unshift($text_stack, ''); 1105 | $strong = $token; 1106 | } 1107 | } else { 1108 | # Here $token_len == 1 1109 | if ($em) { 1110 | if (strlen($token_stack[0]) == 1) { 1111 | # Closing emphasis marker: 1112 | array_shift($token_stack); 1113 | $span = array_shift($text_stack); 1114 | $span = $this->runSpanGamut($span); 1115 | $span = "$span"; 1116 | $text_stack[0] .= $this->hashPart($span); 1117 | $em = ''; 1118 | } else { 1119 | $text_stack[0] .= $token; 1120 | } 1121 | } else { 1122 | array_unshift($token_stack, $token); 1123 | array_unshift($text_stack, ''); 1124 | $em = $token; 1125 | } 1126 | } 1127 | } 1128 | return $text_stack[0]; 1129 | } 1130 | 1131 | 1132 | function doBlockQuotes($text) { 1133 | $text = preg_replace_callback('/ 1134 | ( # Wrap whole match in $1 1135 | (?> 1136 | ^[ ]*>[ ]? # ">" at the start of a line 1137 | .+\n # rest of the first line 1138 | (.+\n)* # subsequent consecutive lines 1139 | \n* # blanks 1140 | )+ 1141 | ) 1142 | /xm', 1143 | array(&$this, '_doBlockQuotes_callback'), $text); 1144 | 1145 | return $text; 1146 | } 1147 | function _doBlockQuotes_callback($matches) { 1148 | $bq = $matches[1]; 1149 | # trim one level of quoting - trim whitespace-only lines 1150 | $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq); 1151 | $bq = $this->runBlockGamut($bq); # recurse 1152 | 1153 | $bq = preg_replace('/^/m', " ", $bq); 1154 | # These leading spaces cause problem with
     content, 
    1155 | 		# so we need to fix that:
    1156 | 		$bq = preg_replace_callback('{(\s*
    .+?
    )}sx', 1157 | array(&$this, '_doBlockQuotes_callback2'), $bq); 1158 | 1159 | return "\n". $this->hashBlock("
    \n$bq\n
    ")."\n\n"; 1160 | } 1161 | function _doBlockQuotes_callback2($matches) { 1162 | $pre = $matches[1]; 1163 | $pre = preg_replace('/^ /m', '', $pre); 1164 | return $pre; 1165 | } 1166 | 1167 | 1168 | function formParagraphs($text) { 1169 | # 1170 | # Params: 1171 | # $text - string to process with html

    tags 1172 | # 1173 | # Strip leading and trailing lines: 1174 | $text = preg_replace('/\A\n+|\n+\z/', '', $text); 1175 | 1176 | $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); 1177 | 1178 | # 1179 | # Wrap

    tags and unhashify HTML blocks 1180 | # 1181 | foreach ($grafs as $key => $value) { 1182 | if (!preg_match('/^B\x1A[0-9]+B$/', $value)) { 1183 | # Is a paragraph. 1184 | $value = $this->runSpanGamut($value); 1185 | $value = preg_replace('/^([ ]*)/', "

    ", $value); 1186 | $value .= "

    "; 1187 | $grafs[$key] = $this->unhash($value); 1188 | } 1189 | else { 1190 | # Is a block. 1191 | # Modify elements of @grafs in-place... 1192 | $graf = $value; 1193 | $block = $this->html_hashes[$graf]; 1194 | $graf = $block; 1195 | // if (preg_match('{ 1196 | // \A 1197 | // ( # $1 =
    tag 1198 | //
    ]* 1200 | // \b 1201 | // markdown\s*=\s* ([\'"]) # $2 = attr quote char 1202 | // 1 1203 | // \2 1204 | // [^>]* 1205 | // > 1206 | // ) 1207 | // ( # $3 = contents 1208 | // .* 1209 | // ) 1210 | // (
    ) # $4 = closing tag 1211 | // \z 1212 | // }xs', $block, $matches)) 1213 | // { 1214 | // list(, $div_open, , $div_content, $div_close) = $matches; 1215 | // 1216 | // # We can't call Markdown(), because that resets the hash; 1217 | // # that initialization code should be pulled into its own sub, though. 1218 | // $div_content = $this->hashHTMLBlocks($div_content); 1219 | // 1220 | // # Run document gamut methods on the content. 1221 | // foreach ($this->document_gamut as $method => $priority) { 1222 | // $div_content = $this->$method($div_content); 1223 | // } 1224 | // 1225 | // $div_open = preg_replace( 1226 | // '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open); 1227 | // 1228 | // $graf = $div_open . "\n" . $div_content . "\n" . $div_close; 1229 | // } 1230 | $grafs[$key] = $graf; 1231 | } 1232 | } 1233 | 1234 | return implode("\n\n", $grafs); 1235 | } 1236 | 1237 | 1238 | function encodeAttribute($text) { 1239 | # 1240 | # Encode text for a double-quoted HTML attribute. This function 1241 | # is *not* suitable for attributes enclosed in single quotes. 1242 | # 1243 | $text = $this->encodeAmpsAndAngles($text); 1244 | $text = str_replace('"', '"', $text); 1245 | return $text; 1246 | } 1247 | 1248 | 1249 | function encodeAmpsAndAngles($text) { 1250 | # 1251 | # Smart processing for ampersands and angle brackets that need to 1252 | # be encoded. Valid character entities are left alone unless the 1253 | # no-entities mode is set. 1254 | # 1255 | if ($this->no_entities) { 1256 | $text = str_replace('&', '&', $text); 1257 | } else { 1258 | # Ampersand-encoding based entirely on Nat Irons's Amputator 1259 | # MT plugin: 1260 | $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/', 1261 | '&', $text);; 1262 | } 1263 | # Encode remaining <'s 1264 | $text = str_replace('<', '<', $text); 1265 | 1266 | return $text; 1267 | } 1268 | 1269 | 1270 | function doAutoLinks($text) { 1271 | $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i', 1272 | array(&$this, '_doAutoLinks_url_callback'), $text); 1273 | 1274 | # Email addresses: 1275 | $text = preg_replace_callback('{ 1276 | < 1277 | (?:mailto:)? 1278 | ( 1279 | (?: 1280 | [-!#$%&\'*+/=?^_`.{|}~\w\x80-\xFF]+ 1281 | | 1282 | ".*?" 1283 | ) 1284 | \@ 1285 | (?: 1286 | [-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+ 1287 | | 1288 | \[[\d.a-fA-F:]+\] # IPv4 & IPv6 1289 | ) 1290 | ) 1291 | > 1292 | }xi', 1293 | array(&$this, '_doAutoLinks_email_callback'), $text); 1294 | 1295 | return $text; 1296 | } 1297 | function _doAutoLinks_url_callback($matches) { 1298 | $url = $this->encodeAttribute($matches[1]); 1299 | $link = "$url"; 1300 | return $this->hashPart($link); 1301 | } 1302 | function _doAutoLinks_email_callback($matches) { 1303 | $address = $matches[1]; 1304 | $link = $this->encodeEmailAddress($address); 1305 | return $this->hashPart($link); 1306 | } 1307 | 1308 | 1309 | function encodeEmailAddress($addr) { 1310 | # 1311 | # Input: an email address, e.g. "foo@example.com" 1312 | # 1313 | # Output: the email address as a mailto link, with each character 1314 | # of the address encoded as either a decimal or hex entity, in 1315 | # the hopes of foiling most address harvesting spam bots. E.g.: 1316 | # 1317 | #

    foo@exampl 1320 | # e.com

    1321 | # 1322 | # Based by a filter by Matthew Wickline, posted to BBEdit-Talk. 1323 | # With some optimizations by Milian Wolff. 1324 | # 1325 | $addr = "mailto:" . $addr; 1326 | $chars = preg_split('/(? $char) { 1330 | $ord = ord($char); 1331 | # Ignore non-ascii chars. 1332 | if ($ord < 128) { 1333 | $r = ($seed * (1 + $key)) % 100; # Pseudo-random function. 1334 | # roughly 10% raw, 45% hex, 45% dec 1335 | # '@' *must* be encoded. I insist. 1336 | if ($r > 90 && $char != '@') /* do nothing */; 1337 | else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';'; 1338 | else $chars[$key] = '&#'.$ord.';'; 1339 | } 1340 | } 1341 | 1342 | $addr = implode('', $chars); 1343 | $text = implode('', array_slice($chars, 7)); # text without `mailto:` 1344 | $addr = "$text"; 1345 | 1346 | return $addr; 1347 | } 1348 | 1349 | 1350 | function parseSpan($str) { 1351 | # 1352 | # Take the string $str and parse it into tokens, hashing embeded HTML, 1353 | # escaped characters and handling code spans. 1354 | # 1355 | $output = ''; 1356 | 1357 | $span_re = '{ 1358 | ( 1359 | \\\\'.$this->escape_chars_re.' 1360 | | 1361 | (?no_markup ? '' : ' 1364 | | 1365 | # comment 1366 | | 1367 | <\?.*?\?> | <%.*?%> # processing instruction 1368 | | 1369 | <[!$]?[-a-zA-Z0-9:_]+ # regular tags 1370 | (?> 1371 | \s 1372 | (?>[^"\'>]+|"[^"]*"|\'[^\']*\')* 1373 | )? 1374 | > 1375 | | 1376 | <[-a-zA-Z0-9:_]+\s*/> # xml-style empty tag 1377 | | 1378 | # closing tag 1379 | ').' 1380 | ) 1381 | }xs'; 1382 | 1383 | while (1) { 1384 | # 1385 | # Each loop iteration seach for either the next tag, the next 1386 | # openning code span marker, or the next escaped character. 1387 | # Each token is then passed to handleSpanToken. 1388 | # 1389 | $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE); 1390 | 1391 | # Create token from text preceding tag. 1392 | if ($parts[0] != "") { 1393 | $output .= $parts[0]; 1394 | } 1395 | 1396 | # Check if we reach the end. 1397 | if (isset($parts[1])) { 1398 | $output .= $this->handleSpanToken($parts[1], $parts[2]); 1399 | $str = $parts[2]; 1400 | } 1401 | else { 1402 | break; 1403 | } 1404 | } 1405 | 1406 | return $output; 1407 | } 1408 | 1409 | 1410 | function handleSpanToken($token, &$str) { 1411 | # 1412 | # Handle $token provided by parseSpan by determining its nature and 1413 | # returning the corresponding value that should replace it. 1414 | # 1415 | switch ($token{0}) { 1416 | case "\\": 1417 | return $this->hashPart("&#". ord($token{1}). ";"); 1418 | case "`": 1419 | # Search for end marker in remaining text. 1420 | if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm', 1421 | $str, $matches)) 1422 | { 1423 | $str = $matches[2]; 1424 | $codespan = $this->makeCodeSpan($matches[1]); 1425 | return $this->hashPart($codespan); 1426 | } 1427 | return $token; // return as text since no ending marker found. 1428 | default: 1429 | return $this->hashPart($token); 1430 | } 1431 | } 1432 | 1433 | 1434 | function outdent($text) { 1435 | # 1436 | # Remove one level of line-leading tabs or spaces 1437 | # 1438 | return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text); 1439 | } 1440 | 1441 | 1442 | # String length function for detab. `_initDetab` will create a function to 1443 | # hanlde UTF-8 if the default function does not exist. 1444 | var $utf8_strlen = 'mb_strlen'; 1445 | 1446 | function detab($text) { 1447 | # 1448 | # Replace tabs with the appropriate amount of space. 1449 | # 1450 | # For each line we separate the line in blocks delemited by 1451 | # tab characters. Then we reconstruct every line by adding the 1452 | # appropriate number of space between each blocks. 1453 | 1454 | $text = preg_replace_callback('/^.*\t.*$/m', 1455 | array(&$this, '_detab_callback'), $text); 1456 | 1457 | return $text; 1458 | } 1459 | function _detab_callback($matches) { 1460 | $line = $matches[0]; 1461 | $strlen = $this->utf8_strlen; # strlen function for UTF-8. 1462 | 1463 | # Split in blocks. 1464 | $blocks = explode("\t", $line); 1465 | # Add each blocks to the line. 1466 | $line = $blocks[0]; 1467 | unset($blocks[0]); # Do not add first block twice. 1468 | foreach ($blocks as $block) { 1469 | # Calculate amount of space, insert spaces, insert block. 1470 | $amount = $this->tab_width - 1471 | $strlen($line, 'UTF-8') % $this->tab_width; 1472 | $line .= str_repeat(" ", $amount) . $block; 1473 | } 1474 | return $line; 1475 | } 1476 | function _initDetab() { 1477 | # 1478 | # Check for the availability of the function in the `utf8_strlen` property 1479 | # (initially `mb_strlen`). If the function is not available, create a 1480 | # function that will loosely count the number of UTF-8 characters with a 1481 | # regular expression. 1482 | # 1483 | if (function_exists($this->utf8_strlen)) return; 1484 | $this->utf8_strlen = create_function('$text', 'return preg_match_all( 1485 | "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/", 1486 | $text, $m);'); 1487 | } 1488 | 1489 | 1490 | function unhash($text) { 1491 | # 1492 | # Swap back in all the tags hashed by _HashHTMLBlocks. 1493 | # 1494 | return preg_replace_callback('/(.)\x1A[0-9]+\1/', 1495 | array(&$this, '_unhash_callback'), $text); 1496 | } 1497 | function _unhash_callback($matches) { 1498 | return $this->html_hashes[$matches[0]]; 1499 | } 1500 | 1501 | } 1502 | 1503 | 1504 | # 1505 | # Markdown Extra Parser Class 1506 | # 1507 | 1508 | class fx_Updater_MarkdownExtra_Parser extends fx_Updater_Markdown_Parser { 1509 | 1510 | ### Configuration Variables ### 1511 | 1512 | # Prefix for footnote ids. 1513 | var $fn_id_prefix = ""; 1514 | 1515 | # Optional title attribute for footnote links and backlinks. 1516 | var $fn_link_title = ""; 1517 | var $fn_backlink_title = ""; 1518 | 1519 | # Optional class attribute for footnote links and backlinks. 1520 | var $fn_link_class = ""; 1521 | var $fn_backlink_class = ""; 1522 | 1523 | # Optional class prefix for fenced code block. 1524 | var $code_class_prefix = ""; 1525 | # Class attribute for code blocks goes on the `code` tag; 1526 | # setting this to true will put attributes on the `pre` tag instead. 1527 | var $code_attr_on_pre = false; 1528 | 1529 | # Predefined abbreviations. 1530 | var $predef_abbr = array(); 1531 | 1532 | 1533 | ### Parser Implementation ### 1534 | 1535 | function fx_Updater_MarkdownExtra_Parser() { 1536 | # 1537 | # Constructor function. Initialize the parser object. 1538 | # 1539 | # Add extra escapable characters before parent constructor 1540 | # initialize the table. 1541 | $this->escape_chars .= ':|'; 1542 | 1543 | # Insert extra document, block, and span transformations. 1544 | # Parent constructor will do the sorting. 1545 | $this->document_gamut += array( 1546 | "doFencedCodeBlocks" => 5, 1547 | "stripFootnotes" => 15, 1548 | "stripAbbreviations" => 25, 1549 | "appendFootnotes" => 50, 1550 | ); 1551 | $this->block_gamut += array( 1552 | "doFencedCodeBlocks" => 5, 1553 | "doTables" => 15, 1554 | "doDefLists" => 45, 1555 | ); 1556 | $this->span_gamut += array( 1557 | "doFootnotes" => 5, 1558 | "doAbbreviations" => 70, 1559 | ); 1560 | 1561 | parent::Markdown_Parser(); 1562 | } 1563 | 1564 | 1565 | # Extra variables used during extra transformations. 1566 | var $footnotes = array(); 1567 | var $footnotes_ordered = array(); 1568 | var $footnotes_ref_count = array(); 1569 | var $footnotes_numbers = array(); 1570 | var $abbr_desciptions = array(); 1571 | var $abbr_word_re = ''; 1572 | 1573 | # Give the current footnote number. 1574 | var $footnote_counter = 1; 1575 | 1576 | 1577 | function setup() { 1578 | # 1579 | # Setting up Extra-specific variables. 1580 | # 1581 | parent::setup(); 1582 | 1583 | $this->footnotes = array(); 1584 | $this->footnotes_ordered = array(); 1585 | $this->footnotes_ref_count = array(); 1586 | $this->footnotes_numbers = array(); 1587 | $this->abbr_desciptions = array(); 1588 | $this->abbr_word_re = ''; 1589 | $this->footnote_counter = 1; 1590 | 1591 | foreach ($this->predef_abbr as $abbr_word => $abbr_desc) { 1592 | if ($this->abbr_word_re) 1593 | $this->abbr_word_re .= '|'; 1594 | $this->abbr_word_re .= preg_quote($abbr_word); 1595 | $this->abbr_desciptions[$abbr_word] = trim($abbr_desc); 1596 | } 1597 | } 1598 | 1599 | function teardown() { 1600 | # 1601 | # Clearing Extra-specific variables. 1602 | # 1603 | $this->footnotes = array(); 1604 | $this->footnotes_ordered = array(); 1605 | $this->footnotes_ref_count = array(); 1606 | $this->footnotes_numbers = array(); 1607 | $this->abbr_desciptions = array(); 1608 | $this->abbr_word_re = ''; 1609 | 1610 | parent::teardown(); 1611 | } 1612 | 1613 | 1614 | ### Extra Attribute Parser ### 1615 | 1616 | # Expression to use to catch attributes (includes the braces) 1617 | var $id_class_attr_catch_re = '\{((?:[ ]*[#.][-_:a-zA-Z0-9]+){1,})[ ]*\}'; 1618 | # Expression to use when parsing in a context when no capture is desired 1619 | var $id_class_attr_nocatch_re = '\{(?:[ ]*[#.][-_:a-zA-Z0-9]+){1,}[ ]*\}'; 1620 | 1621 | function doExtraAttributes($tag_name, $attr) { 1622 | # 1623 | # Parse attributes caught by the $this->id_class_attr_catch_re expression 1624 | # and return the HTML-formatted list of attributes. 1625 | # 1626 | # Currently supported attributes are .class and #id. 1627 | # 1628 | if (empty($attr)) return ""; 1629 | 1630 | # Split on components 1631 | preg_match_all('/[#.][-_:a-zA-Z0-9]+/', $attr, $matches); 1632 | $elements = $matches[0]; 1633 | 1634 | # handle classes and ids (only first id taken into account) 1635 | $classes = array(); 1636 | $id = false; 1637 | foreach ($elements as $element) { 1638 | if ($element{0} == '.') { 1639 | $classes[] = substr($element, 1); 1640 | } else if ($element{0} == '#') { 1641 | if ($id === false) $id = substr($element, 1); 1642 | } 1643 | } 1644 | 1645 | # compose attributes as string 1646 | $attr_str = ""; 1647 | if (!empty($id)) { 1648 | $attr_str .= ' id="'.$id.'"'; 1649 | } 1650 | if (!empty($classes)) { 1651 | $attr_str .= ' class="'.implode(" ", $classes).'"'; 1652 | } 1653 | return $attr_str; 1654 | } 1655 | 1656 | 1657 | function stripLinkDefinitions($text) { 1658 | # 1659 | # Strips link definitions from text, stores the URLs and titles in 1660 | # hash references. 1661 | # 1662 | $less_than_tab = $this->tab_width - 1; 1663 | 1664 | # Link defs are in the form: ^[id]: url "optional title" 1665 | $text = preg_replace_callback('{ 1666 | ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1 1667 | [ ]* 1668 | \n? # maybe *one* newline 1669 | [ ]* 1670 | (?: 1671 | <(.+?)> # url = $2 1672 | | 1673 | (\S+?) # url = $3 1674 | ) 1675 | [ ]* 1676 | \n? # maybe one newline 1677 | [ ]* 1678 | (?: 1679 | (?<=\s) # lookbehind for whitespace 1680 | ["(] 1681 | (.*?) # title = $4 1682 | [")] 1683 | [ ]* 1684 | )? # title is optional 1685 | (?:[ ]* '.$this->id_class_attr_catch_re.' )? # $5 = extra id & class attr 1686 | (?:\n+|\Z) 1687 | }xm', 1688 | array(&$this, '_stripLinkDefinitions_callback'), 1689 | $text); 1690 | return $text; 1691 | } 1692 | function _stripLinkDefinitions_callback($matches) { 1693 | $link_id = strtolower($matches[1]); 1694 | $url = $matches[2] == '' ? $matches[3] : $matches[2]; 1695 | $this->urls[$link_id] = $url; 1696 | $this->titles[$link_id] =& $matches[4]; 1697 | $this->ref_attr[$link_id] = $this->doExtraAttributes("", $dummy =& $matches[5]); 1698 | return ''; # String that will replace the block 1699 | } 1700 | 1701 | 1702 | ### HTML Block Parser ### 1703 | 1704 | # Tags that are always treated as block tags: 1705 | var $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend|article|section|nav|aside|hgroup|header|footer|figcaption'; 1706 | 1707 | # Tags treated as block tags only if the opening tag is alone on its line: 1708 | var $context_block_tags_re = 'script|noscript|ins|del|iframe|object|source|track|param|math|svg|canvas|audio|video'; 1709 | 1710 | # Tags where markdown="1" default to span mode: 1711 | var $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address'; 1712 | 1713 | # Tags which must not have their contents modified, no matter where 1714 | # they appear: 1715 | var $clean_tags_re = 'script|math|svg'; 1716 | 1717 | # Tags that do not need to be closed. 1718 | var $auto_close_tags_re = 'hr|img|param|source|track'; 1719 | 1720 | 1721 | function hashHTMLBlocks($text) { 1722 | # 1723 | # Hashify HTML Blocks and "clean tags". 1724 | # 1725 | # We only want to do this for block-level HTML tags, such as headers, 1726 | # lists, and tables. That's because we still want to wrap

    s around 1727 | # "paragraphs" that are wrapped in non-block-level tags, such as anchors, 1728 | # phrase emphasis, and spans. The list of tags we're looking for is 1729 | # hard-coded. 1730 | # 1731 | # This works by calling _HashHTMLBlocks_InMarkdown, which then calls 1732 | # _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1" 1733 | # attribute is found within a tag, _HashHTMLBlocks_InHTML calls back 1734 | # _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag. 1735 | # These two functions are calling each other. It's recursive! 1736 | # 1737 | if ($this->no_markup) return $text; 1738 | 1739 | # 1740 | # Call the HTML-in-Markdown hasher. 1741 | # 1742 | list($text, ) = $this->_hashHTMLBlocks_inMarkdown($text); 1743 | 1744 | return $text; 1745 | } 1746 | function _hashHTMLBlocks_inMarkdown($text, $indent = 0, 1747 | $enclosing_tag_re = '', $span = false) 1748 | { 1749 | # 1750 | # Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags. 1751 | # 1752 | # * $indent is the number of space to be ignored when checking for code 1753 | # blocks. This is important because if we don't take the indent into 1754 | # account, something like this (which looks right) won't work as expected: 1755 | # 1756 | #

    1757 | #
    1758 | # Hello World. <-- Is this a Markdown code block or text? 1759 | #
    <-- Is this a Markdown code block or a real tag? 1760 | #
    1761 | # 1762 | # If you don't like this, just don't indent the tag on which 1763 | # you apply the markdown="1" attribute. 1764 | # 1765 | # * If $enclosing_tag_re is not empty, stops at the first unmatched closing 1766 | # tag with that name. Nested tags supported. 1767 | # 1768 | # * If $span is true, text inside must treated as span. So any double 1769 | # newline will be replaced by a single newline so that it does not create 1770 | # paragraphs. 1771 | # 1772 | # Returns an array of that form: ( processed text , remaining text ) 1773 | # 1774 | if ($text === '') return array('', ''); 1775 | 1776 | # Regex to check for the presense of newlines around a block tag. 1777 | $newline_before_re = '/(?:^\n?|\n\n)*$/'; 1778 | $newline_after_re = 1779 | '{ 1780 | ^ # Start of text following the tag. 1781 | (?>[ ]*)? # Optional comment. 1782 | [ ]*\n # Must be followed by newline. 1783 | }xs'; 1784 | 1785 | # Regex to match any tag. 1786 | $block_tag_re = 1787 | '{ 1788 | ( # $2: Capture whole tag. 1789 | # Tag name. 1791 | '.$this->block_tags_re.' | 1792 | '.$this->context_block_tags_re.' | 1793 | '.$this->clean_tags_re.' | 1794 | (?!\s)'.$enclosing_tag_re.' 1795 | ) 1796 | (?: 1797 | (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name. 1798 | (?> 1799 | ".*?" | # Double quotes (can contain `>`) 1800 | \'.*?\' | # Single quotes (can contain `>`) 1801 | .+? # Anything but quotes and `>`. 1802 | )*? 1803 | )? 1804 | > # End of tag. 1805 | | 1806 | # HTML Comment 1807 | | 1808 | <\?.*?\?> | <%.*?%> # Processing instruction 1809 | | 1810 | # CData Block 1811 | | 1812 | # Code span marker 1813 | `+ 1814 | '. ( !$span ? ' # If not in span. 1815 | | 1816 | # Indented code block 1817 | (?: ^[ ]*\n | ^ | \n[ ]*\n ) 1818 | [ ]{'.($indent+4).'}[^\n]* \n 1819 | (?> 1820 | (?: [ ]{'.($indent+4).'}[^\n]* | [ ]* ) \n 1821 | )* 1822 | | 1823 | # Fenced code block marker 1824 | (?<= ^ | \n ) 1825 | [ ]{0,'.($indent+3).'}~{3,} 1826 | [ ]* 1827 | (?: 1828 | \.?[-_:a-zA-Z0-9]+ # standalone class name 1829 | | 1830 | '.$this->id_class_attr_nocatch_re.' # extra attributes 1831 | )? 1832 | [ ]* 1833 | \n 1834 | ' : '' ). ' # End (if not is span). 1835 | ) 1836 | }xs'; 1837 | 1838 | 1839 | $depth = 0; # Current depth inside the tag tree. 1840 | $parsed = ""; # Parsed text that will be returned. 1841 | 1842 | # 1843 | # Loop through every tag until we find the closing tag of the parent 1844 | # or loop until reaching the end of text if no parent tag specified. 1845 | # 1846 | do { 1847 | # 1848 | # Split the text using the first $tag_match pattern found. 1849 | # Text before pattern will be first in the array, text after 1850 | # pattern will be at the end, and between will be any catches made 1851 | # by the pattern. 1852 | # 1853 | $parts = preg_split($block_tag_re, $text, 2, 1854 | PREG_SPLIT_DELIM_CAPTURE); 1855 | 1856 | # If in Markdown span mode, add a empty-string span-level hash 1857 | # after each newline to prevent triggering any block element. 1858 | if ($span) { 1859 | $void = $this->hashPart("", ':'); 1860 | $newline = "$void\n"; 1861 | $parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void; 1862 | } 1863 | 1864 | $parsed .= $parts[0]; # Text before current tag. 1865 | 1866 | # If end of $text has been reached. Stop loop. 1867 | if (count($parts) < 3) { 1868 | $text = ""; 1869 | break; 1870 | } 1871 | 1872 | $tag = $parts[1]; # Tag to handle. 1873 | $text = $parts[2]; # Remaining text after current tag. 1874 | $tag_re = preg_quote($tag); # For use in a regular expression. 1875 | 1876 | # 1877 | # Check for: Code span marker 1878 | # 1879 | if ($tag{0} == "`") { 1880 | # Find corresponding end marker. 1881 | $tag_re = preg_quote($tag); 1882 | if (preg_match('{^(?>.+?|\n(?!\n))*?(?.*\n)*?[ ]{'.($fence_indent).'}'.$fence_re.'[ ]*(?:\n|$)}', $text, 1902 | $matches)) 1903 | { 1904 | # End marker found: pass text unchanged until marker. 1905 | $parsed .= $tag . $matches[0]; 1906 | $text = substr($text, strlen($matches[0])); 1907 | } 1908 | else { 1909 | # No end marker: just skip it. 1910 | $parsed .= $tag; 1911 | } 1912 | } 1913 | # 1914 | # Check for: Indented code block. 1915 | # 1916 | else if ($tag{0} == "\n" || $tag{0} == " ") { 1917 | # Indented code block: pass it unchanged, will be handled 1918 | # later. 1919 | $parsed .= $tag; 1920 | } 1921 | # 1922 | # Check for: Opening Block level tag or 1923 | # Opening Context Block tag (like ins and del) 1924 | # used as a block tag (tag is alone on it's line). 1925 | # 1926 | else if (preg_match('{^<(?:'.$this->block_tags_re.')\b}', $tag) || 1927 | ( preg_match('{^<(?:'.$this->context_block_tags_re.')\b}', $tag) && 1928 | preg_match($newline_before_re, $parsed) && 1929 | preg_match($newline_after_re, $text) ) 1930 | ) 1931 | { 1932 | # Need to parse tag and following text using the HTML parser. 1933 | list($block_text, $text) = 1934 | $this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true); 1935 | 1936 | # Make sure it stays outside of any paragraph by adding newlines. 1937 | $parsed .= "\n\n$block_text\n\n"; 1938 | } 1939 | # 1940 | # Check for: Clean tag (like script, math) 1941 | # HTML Comments, processing instructions. 1942 | # 1943 | else if (preg_match('{^<(?:'.$this->clean_tags_re.')\b}', $tag) || 1944 | $tag{1} == '!' || $tag{1} == '?') 1945 | { 1946 | # Need to parse tag and following text using the HTML parser. 1947 | # (don't check for markdown attribute) 1948 | list($block_text, $text) = 1949 | $this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false); 1950 | 1951 | $parsed .= $block_text; 1952 | } 1953 | # 1954 | # Check for: Tag with same name as enclosing tag. 1955 | # 1956 | else if ($enclosing_tag_re !== '' && 1957 | # Same name as enclosing tag. 1958 | preg_match('{^= 0); 1981 | 1982 | return array($parsed, $text); 1983 | } 1984 | function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) { 1985 | # 1986 | # Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags. 1987 | # 1988 | # * Calls $hash_method to convert any blocks. 1989 | # * Stops when the first opening tag closes. 1990 | # * $md_attr indicate if the use of the `markdown="1"` attribute is allowed. 1991 | # (it is not inside clean tags) 1992 | # 1993 | # Returns an array of that form: ( processed text , remaining text ) 1994 | # 1995 | if ($text === '') return array('', ''); 1996 | 1997 | # Regex to match `markdown` attribute inside of a tag. 1998 | $markdown_attr_re = ' 1999 | { 2000 | \s* # Eat whitespace before the `markdown` attribute 2001 | markdown 2002 | \s*=\s* 2003 | (?> 2004 | (["\']) # $1: quote delimiter 2005 | (.*?) # $2: attribute value 2006 | \1 # matching delimiter 2007 | | 2008 | ([^\s>]*) # $3: unquoted attribute value 2009 | ) 2010 | () # $4: make $3 always defined (avoid warnings) 2011 | }xs'; 2012 | 2013 | # Regex to match any tag. 2014 | $tag_re = '{ 2015 | ( # $2: Capture whole tag. 2016 | 2021 | ".*?" | # Double quotes (can contain `>`) 2022 | \'.*?\' | # Single quotes (can contain `>`) 2023 | .+? # Anything but quotes and `>`. 2024 | )*? 2025 | )? 2026 | > # End of tag. 2027 | | 2028 | # HTML Comment 2029 | | 2030 | <\?.*?\?> | <%.*?%> # Processing instruction 2031 | | 2032 | # CData Block 2033 | ) 2034 | }xs'; 2035 | 2036 | $original_text = $text; # Save original text in case of faliure. 2037 | 2038 | $depth = 0; # Current depth inside the tag tree. 2039 | $block_text = ""; # Temporary text holder for current text. 2040 | $parsed = ""; # Parsed text that will be returned. 2041 | 2042 | # 2043 | # Get the name of the starting tag. 2044 | # (This pattern makes $base_tag_name_re safe without quoting.) 2045 | # 2046 | if (preg_match('/^<([\w:$]*)\b/', $text, $matches)) 2047 | $base_tag_name_re = $matches[1]; 2048 | 2049 | # 2050 | # Loop through every tag until we find the corresponding closing tag. 2051 | # 2052 | do { 2053 | # 2054 | # Split the text using the first $tag_match pattern found. 2055 | # Text before pattern will be first in the array, text after 2056 | # pattern will be at the end, and between will be any catches made 2057 | # by the pattern. 2058 | # 2059 | $parts = preg_split($tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE); 2060 | 2061 | if (count($parts) < 3) { 2062 | # 2063 | # End of $text reached with unbalenced tag(s). 2064 | # In that case, we return original text unchanged and pass the 2065 | # first character as filtered to prevent an infinite loop in the 2066 | # parent function. 2067 | # 2068 | return array($original_text{0}, substr($original_text, 1)); 2069 | } 2070 | 2071 | $block_text .= $parts[0]; # Text before current tag. 2072 | $tag = $parts[1]; # Tag to handle. 2073 | $text = $parts[2]; # Remaining text after current tag. 2074 | 2075 | # 2076 | # Check for: Auto-close tag (like
    ) 2077 | # Comments and Processing Instructions. 2078 | # 2079 | if (preg_match('{^auto_close_tags_re.')\b}', $tag) || 2080 | $tag{1} == '!' || $tag{1} == '?') 2081 | { 2082 | # Just add the tag to the block as if it was text. 2083 | $block_text .= $tag; 2084 | } 2085 | else { 2086 | # 2087 | # Increase/decrease nested tag count. Only do so if 2088 | # the tag's name match base tag's. 2089 | # 2090 | if (preg_match('{^mode = $attr_m[2] . $attr_m[3]; 2107 | $span_mode = $this->mode == 'span' || $this->mode != 'block' && 2108 | preg_match('{^<(?:'.$this->contain_span_tags_re.')\b}', $tag); 2109 | 2110 | # Calculate indent before tag. 2111 | if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) { 2112 | $strlen = $this->utf8_strlen; 2113 | $indent = $strlen($matches[1], 'UTF-8'); 2114 | } else { 2115 | $indent = 0; 2116 | } 2117 | 2118 | # End preceding block with this tag. 2119 | $block_text .= $tag; 2120 | $parsed .= $this->$hash_method($block_text); 2121 | 2122 | # Get enclosing tag name for the ParseMarkdown function. 2123 | # (This pattern makes $tag_name_re safe without quoting.) 2124 | preg_match('/^<([\w:$]*)\b/', $tag, $matches); 2125 | $tag_name_re = $matches[1]; 2126 | 2127 | # Parse the content using the HTML-in-Markdown parser. 2128 | list ($block_text, $text) 2129 | = $this->_hashHTMLBlocks_inMarkdown($text, $indent, 2130 | $tag_name_re, $span_mode); 2131 | 2132 | # Outdent markdown text. 2133 | if ($indent > 0) { 2134 | $block_text = preg_replace("/^[ ]{1,$indent}/m", "", 2135 | $block_text); 2136 | } 2137 | 2138 | # Append tag content to parsed text. 2139 | if (!$span_mode) $parsed .= "\n\n$block_text\n\n"; 2140 | else $parsed .= "$block_text"; 2141 | 2142 | # Start over with a new block. 2143 | $block_text = ""; 2144 | } 2145 | else $block_text .= $tag; 2146 | } 2147 | 2148 | } while ($depth > 0); 2149 | 2150 | # 2151 | # Hash last block text that wasn't processed inside the loop. 2152 | # 2153 | $parsed .= $this->$hash_method($block_text); 2154 | 2155 | return array($parsed, $text); 2156 | } 2157 | 2158 | 2159 | function hashClean($text) { 2160 | # 2161 | # Called whenever a tag must be hashed when a function inserts a "clean" tag 2162 | # in $text, it passes through this function and is automaticaly escaped, 2163 | # blocking invalid nested overlap. 2164 | # 2165 | return $this->hashPart($text, 'C'); 2166 | } 2167 | 2168 | 2169 | function doAnchors($text) { 2170 | # 2171 | # Turn Markdown link shortcuts into XHTML tags. 2172 | # 2173 | if ($this->in_anchor) return $text; 2174 | $this->in_anchor = true; 2175 | 2176 | # 2177 | # First, handle reference-style links: [link text] [id] 2178 | # 2179 | $text = preg_replace_callback('{ 2180 | ( # wrap whole match in $1 2181 | \[ 2182 | ('.$this->nested_brackets_re.') # link text = $2 2183 | \] 2184 | 2185 | [ ]? # one optional space 2186 | (?:\n[ ]*)? # one optional newline followed by spaces 2187 | 2188 | \[ 2189 | (.*?) # id = $3 2190 | \] 2191 | ) 2192 | }xs', 2193 | array(&$this, '_doAnchors_reference_callback'), $text); 2194 | 2195 | # 2196 | # Next, inline-style links: [link text](url "optional title") 2197 | # 2198 | $text = preg_replace_callback('{ 2199 | ( # wrap whole match in $1 2200 | \[ 2201 | ('.$this->nested_brackets_re.') # link text = $2 2202 | \] 2203 | \( # literal paren 2204 | [ \n]* 2205 | (?: 2206 | <(.+?)> # href = $3 2207 | | 2208 | ('.$this->nested_url_parenthesis_re.') # href = $4 2209 | ) 2210 | [ \n]* 2211 | ( # $5 2212 | ([\'"]) # quote char = $6 2213 | (.*?) # Title = $7 2214 | \6 # matching quote 2215 | [ \n]* # ignore any spaces/tabs between closing quote and ) 2216 | )? # title is optional 2217 | \) 2218 | (?:[ ]? '.$this->id_class_attr_catch_re.' )? # $8 = id/class attributes 2219 | ) 2220 | }xs', 2221 | array(&$this, '_doAnchors_inline_callback'), $text); 2222 | 2223 | # 2224 | # Last, handle reference-style shortcuts: [link text] 2225 | # These must come last in case you've also got [link text][1] 2226 | # or [link text](/foo) 2227 | # 2228 | $text = preg_replace_callback('{ 2229 | ( # wrap whole match in $1 2230 | \[ 2231 | ([^\[\]]+) # link text = $2; can\'t contain [ or ] 2232 | \] 2233 | ) 2234 | }xs', 2235 | array(&$this, '_doAnchors_reference_callback'), $text); 2236 | 2237 | $this->in_anchor = false; 2238 | return $text; 2239 | } 2240 | function _doAnchors_reference_callback($matches) { 2241 | $whole_match = $matches[1]; 2242 | $link_text = $matches[2]; 2243 | $link_id =& $matches[3]; 2244 | 2245 | if ($link_id == "") { 2246 | # for shortcut links like [this][] or [this]. 2247 | $link_id = $link_text; 2248 | } 2249 | 2250 | # lower-case and turn embedded newlines into spaces 2251 | $link_id = strtolower($link_id); 2252 | $link_id = preg_replace('{[ ]?\n}', ' ', $link_id); 2253 | 2254 | if (isset($this->urls[$link_id])) { 2255 | $url = $this->urls[$link_id]; 2256 | $url = $this->encodeAttribute($url); 2257 | 2258 | $result = "titles[$link_id] ) ) { 2260 | $title = $this->titles[$link_id]; 2261 | $title = $this->encodeAttribute($title); 2262 | $result .= " title=\"$title\""; 2263 | } 2264 | if (isset($this->ref_attr[$link_id])) 2265 | $result .= $this->ref_attr[$link_id]; 2266 | 2267 | $link_text = $this->runSpanGamut($link_text); 2268 | $result .= ">$link_text"; 2269 | $result = $this->hashPart($result); 2270 | } 2271 | else { 2272 | $result = $whole_match; 2273 | } 2274 | return $result; 2275 | } 2276 | function _doAnchors_inline_callback($matches) { 2277 | $whole_match = $matches[1]; 2278 | $link_text = $this->runSpanGamut($matches[2]); 2279 | $url = $matches[3] == '' ? $matches[4] : $matches[3]; 2280 | $title =& $matches[7]; 2281 | $attr = $this->doExtraAttributes("a", $dummy =& $matches[8]); 2282 | 2283 | 2284 | $url = $this->encodeAttribute($url); 2285 | 2286 | $result = "encodeAttribute($title); 2289 | $result .= " title=\"$title\""; 2290 | } 2291 | $result .= $attr; 2292 | 2293 | $link_text = $this->runSpanGamut($link_text); 2294 | $result .= ">$link_text"; 2295 | 2296 | return $this->hashPart($result); 2297 | } 2298 | 2299 | 2300 | function doImages($text) { 2301 | # 2302 | # Turn Markdown image shortcuts into tags. 2303 | # 2304 | # 2305 | # First, handle reference-style labeled images: ![alt text][id] 2306 | # 2307 | $text = preg_replace_callback('{ 2308 | ( # wrap whole match in $1 2309 | !\[ 2310 | ('.$this->nested_brackets_re.') # alt text = $2 2311 | \] 2312 | 2313 | [ ]? # one optional space 2314 | (?:\n[ ]*)? # one optional newline followed by spaces 2315 | 2316 | \[ 2317 | (.*?) # id = $3 2318 | \] 2319 | 2320 | ) 2321 | }xs', 2322 | array(&$this, '_doImages_reference_callback'), $text); 2323 | 2324 | # 2325 | # Next, handle inline images: ![alt text](url "optional title") 2326 | # Don't forget: encode * and _ 2327 | # 2328 | $text = preg_replace_callback('{ 2329 | ( # wrap whole match in $1 2330 | !\[ 2331 | ('.$this->nested_brackets_re.') # alt text = $2 2332 | \] 2333 | \s? # One optional whitespace character 2334 | \( # literal paren 2335 | [ \n]* 2336 | (?: 2337 | <(\S*)> # src url = $3 2338 | | 2339 | ('.$this->nested_url_parenthesis_re.') # src url = $4 2340 | ) 2341 | [ \n]* 2342 | ( # $5 2343 | ([\'"]) # quote char = $6 2344 | (.*?) # title = $7 2345 | \6 # matching quote 2346 | [ \n]* 2347 | )? # title is optional 2348 | \) 2349 | (?:[ ]? '.$this->id_class_attr_catch_re.' )? # $8 = id/class attributes 2350 | ) 2351 | }xs', 2352 | array(&$this, '_doImages_inline_callback'), $text); 2353 | 2354 | return $text; 2355 | } 2356 | function _doImages_reference_callback($matches) { 2357 | $whole_match = $matches[1]; 2358 | $alt_text = $matches[2]; 2359 | $link_id = strtolower($matches[3]); 2360 | 2361 | if ($link_id == "") { 2362 | $link_id = strtolower($alt_text); # for shortcut links like ![this][]. 2363 | } 2364 | 2365 | $alt_text = $this->encodeAttribute($alt_text); 2366 | if (isset($this->urls[$link_id])) { 2367 | $url = $this->encodeAttribute($this->urls[$link_id]); 2368 | $result = "\"$alt_text\"";titles[$link_id])) { 2370 | $title = $this->titles[$link_id]; 2371 | $title = $this->encodeAttribute($title); 2372 | $result .= " title=\"$title\""; 2373 | } 2374 | if (isset($this->ref_attr[$link_id])) 2375 | $result .= $this->ref_attr[$link_id]; 2376 | $result .= $this->empty_element_suffix; 2377 | $result = $this->hashPart($result); 2378 | } 2379 | else { 2380 | # If there's no such link ID, leave intact: 2381 | $result = $whole_match; 2382 | } 2383 | 2384 | return $result; 2385 | } 2386 | function _doImages_inline_callback($matches) { 2387 | $whole_match = $matches[1]; 2388 | $alt_text = $matches[2]; 2389 | $url = $matches[3] == '' ? $matches[4] : $matches[3]; 2390 | $title =& $matches[7]; 2391 | $attr = $this->doExtraAttributes("img", $dummy =& $matches[8]); 2392 | 2393 | $alt_text = $this->encodeAttribute($alt_text); 2394 | $url = $this->encodeAttribute($url); 2395 | $result = "\"$alt_text\"";encodeAttribute($title); 2398 | $result .= " title=\"$title\""; # $title already quoted 2399 | } 2400 | $result .= $attr; 2401 | $result .= $this->empty_element_suffix; 2402 | 2403 | return $this->hashPart($result); 2404 | } 2405 | 2406 | 2407 | function doHeaders($text) { 2408 | # 2409 | # Redefined to add id and class attribute support. 2410 | # 2411 | # Setext-style headers: 2412 | # Header 1 {#header1} 2413 | # ======== 2414 | # 2415 | # Header 2 {#header2 .class1 .class2} 2416 | # -------- 2417 | # 2418 | $text = preg_replace_callback( 2419 | '{ 2420 | (^.+?) # $1: Header text 2421 | (?:[ ]+ '.$this->id_class_attr_catch_re.' )? # $3 = id/class attributes 2422 | [ ]*\n(=+|-+)[ ]*\n+ # $3: Header footer 2423 | }mx', 2424 | array(&$this, '_doHeaders_callback_setext'), $text); 2425 | 2426 | # atx-style headers: 2427 | # # Header 1 {#header1} 2428 | # ## Header 2 {#header2} 2429 | # ## Header 2 with closing hashes ## {#header3.class1.class2} 2430 | # ... 2431 | # ###### Header 6 {.class2} 2432 | # 2433 | $text = preg_replace_callback('{ 2434 | ^(\#{1,6}) # $1 = string of #\'s 2435 | [ ]* 2436 | (.+?) # $2 = Header text 2437 | [ ]* 2438 | \#* # optional closing #\'s (not counted) 2439 | (?:[ ]+ '.$this->id_class_attr_catch_re.' )? # $3 = id/class attributes 2440 | [ ]* 2441 | \n+ 2442 | }xm', 2443 | array(&$this, '_doHeaders_callback_atx'), $text); 2444 | 2445 | return $text; 2446 | } 2447 | function _doHeaders_callback_setext($matches) { 2448 | if ($matches[3] == '-' && preg_match('{^- }', $matches[1])) 2449 | return $matches[0]; 2450 | $level = $matches[3]{0} == '=' ? 1 : 2; 2451 | $attr = $this->doExtraAttributes("h$level", $dummy =& $matches[2]); 2452 | $block = "".$this->runSpanGamut($matches[1]).""; 2453 | return "\n" . $this->hashBlock($block) . "\n\n"; 2454 | } 2455 | function _doHeaders_callback_atx($matches) { 2456 | $level = strlen($matches[1]); 2457 | $attr = $this->doExtraAttributes("h$level", $dummy =& $matches[3]); 2458 | $block = "".$this->runSpanGamut($matches[2]).""; 2459 | return "\n" . $this->hashBlock($block) . "\n\n"; 2460 | } 2461 | 2462 | 2463 | function doTables($text) { 2464 | # 2465 | # Form HTML tables. 2466 | # 2467 | $less_than_tab = $this->tab_width - 1; 2468 | # 2469 | # Find tables with leading pipe. 2470 | # 2471 | # | Header 1 | Header 2 2472 | # | -------- | -------- 2473 | # | Cell 1 | Cell 2 2474 | # | Cell 3 | Cell 4 2475 | # 2476 | $text = preg_replace_callback(' 2477 | { 2478 | ^ # Start of a line 2479 | [ ]{0,'.$less_than_tab.'} # Allowed whitespace. 2480 | [|] # Optional leading pipe (present) 2481 | (.+) \n # $1: Header row (at least one pipe) 2482 | 2483 | [ ]{0,'.$less_than_tab.'} # Allowed whitespace. 2484 | [|] ([ ]*[-:]+[-| :]*) \n # $2: Header underline 2485 | 2486 | ( # $3: Cells 2487 | (?> 2488 | [ ]* # Allowed whitespace. 2489 | [|] .* \n # Row content. 2490 | )* 2491 | ) 2492 | (?=\n|\Z) # Stop at final double newline. 2493 | }xm', 2494 | array(&$this, '_doTable_leadingPipe_callback'), $text); 2495 | 2496 | # 2497 | # Find tables without leading pipe. 2498 | # 2499 | # Header 1 | Header 2 2500 | # -------- | -------- 2501 | # Cell 1 | Cell 2 2502 | # Cell 3 | Cell 4 2503 | # 2504 | $text = preg_replace_callback(' 2505 | { 2506 | ^ # Start of a line 2507 | [ ]{0,'.$less_than_tab.'} # Allowed whitespace. 2508 | (\S.*[|].*) \n # $1: Header row (at least one pipe) 2509 | 2510 | [ ]{0,'.$less_than_tab.'} # Allowed whitespace. 2511 | ([-:]+[ ]*[|][-| :]*) \n # $2: Header underline 2512 | 2513 | ( # $3: Cells 2514 | (?> 2515 | .* [|] .* \n # Row content 2516 | )* 2517 | ) 2518 | (?=\n|\Z) # Stop at final double newline. 2519 | }xm', 2520 | array(&$this, '_DoTable_callback'), $text); 2521 | 2522 | return $text; 2523 | } 2524 | function _doTable_leadingPipe_callback($matches) { 2525 | $head = $matches[1]; 2526 | $underline = $matches[2]; 2527 | $content = $matches[3]; 2528 | 2529 | # Remove leading pipe for each row. 2530 | $content = preg_replace('/^ *[|]/m', '', $content); 2531 | 2532 | return $this->_doTable_callback(array($matches[0], $head, $underline, $content)); 2533 | } 2534 | function _doTable_callback($matches) { 2535 | $head = $matches[1]; 2536 | $underline = $matches[2]; 2537 | $content = $matches[3]; 2538 | 2539 | # Remove any tailing pipes for each line. 2540 | $head = preg_replace('/[|] *$/m', '', $head); 2541 | $underline = preg_replace('/[|] *$/m', '', $underline); 2542 | $content = preg_replace('/[|] *$/m', '', $content); 2543 | 2544 | # Reading alignement from header underline. 2545 | $separators = preg_split('/ *[|] */', $underline); 2546 | foreach ($separators as $n => $s) { 2547 | if (preg_match('/^ *-+: *$/', $s)) $attr[$n] = ' align="right"'; 2548 | else if (preg_match('/^ *:-+: *$/', $s))$attr[$n] = ' align="center"'; 2549 | else if (preg_match('/^ *:-+ *$/', $s)) $attr[$n] = ' align="left"'; 2550 | else $attr[$n] = ''; 2551 | } 2552 | 2553 | # Parsing span elements, including code spans, character escapes, 2554 | # and inline HTML tags, so that pipes inside those gets ignored. 2555 | $head = $this->parseSpan($head); 2556 | $headers = preg_split('/ *[|] */', $head); 2557 | $col_count = count($headers); 2558 | $attr = array_pad($attr, $col_count, ''); 2559 | 2560 | # Write column headers. 2561 | $text = "\n"; 2562 | $text .= "\n"; 2563 | $text .= "\n"; 2564 | foreach ($headers as $n => $header) 2565 | $text .= " ".$this->runSpanGamut(trim($header))."\n"; 2566 | $text .= "\n"; 2567 | $text .= "\n"; 2568 | 2569 | # Split content by row. 2570 | $rows = explode("\n", trim($content, "\n")); 2571 | 2572 | $text .= "\n"; 2573 | foreach ($rows as $row) { 2574 | # Parsing span elements, including code spans, character escapes, 2575 | # and inline HTML tags, so that pipes inside those gets ignored. 2576 | $row = $this->parseSpan($row); 2577 | 2578 | # Split row by cell. 2579 | $row_cells = preg_split('/ *[|] */', $row, $col_count); 2580 | $row_cells = array_pad($row_cells, $col_count, ''); 2581 | 2582 | $text .= "\n"; 2583 | foreach ($row_cells as $n => $cell) 2584 | $text .= " ".$this->runSpanGamut(trim($cell))."\n"; 2585 | $text .= "\n"; 2586 | } 2587 | $text .= "\n"; 2588 | $text .= "
    "; 2589 | 2590 | return $this->hashBlock($text) . "\n"; 2591 | } 2592 | 2593 | 2594 | function doDefLists($text) { 2595 | # 2596 | # Form HTML definition lists. 2597 | # 2598 | $less_than_tab = $this->tab_width - 1; 2599 | 2600 | # Re-usable pattern to match any entire dl list: 2601 | $whole_list_re = '(?> 2602 | ( # $1 = whole list 2603 | ( # $2 2604 | [ ]{0,'.$less_than_tab.'} 2605 | ((?>.*\S.*\n)+) # $3 = defined term 2606 | \n? 2607 | [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition 2608 | ) 2609 | (?s:.+?) 2610 | ( # $4 2611 | \z 2612 | | 2613 | \n{2,} 2614 | (?=\S) 2615 | (?! # Negative lookahead for another term 2616 | [ ]{0,'.$less_than_tab.'} 2617 | (?: \S.*\n )+? # defined term 2618 | \n? 2619 | [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition 2620 | ) 2621 | (?! # Negative lookahead for another definition 2622 | [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition 2623 | ) 2624 | ) 2625 | ) 2626 | )'; // mx 2627 | 2628 | $text = preg_replace_callback('{ 2629 | (?>\A\n?|(?<=\n\n)) 2630 | '.$whole_list_re.' 2631 | }mx', 2632 | array(&$this, '_doDefLists_callback'), $text); 2633 | 2634 | return $text; 2635 | } 2636 | function _doDefLists_callback($matches) { 2637 | # Re-usable patterns to match list item bullets and number markers: 2638 | $list = $matches[1]; 2639 | 2640 | # Turn double returns into triple returns, so that we can make a 2641 | # paragraph for the last item in a list, if necessary: 2642 | $result = trim($this->processDefListItems($list)); 2643 | $result = "
    \n" . $result . "\n
    "; 2644 | return $this->hashBlock($result) . "\n\n"; 2645 | } 2646 | 2647 | 2648 | function processDefListItems($list_str) { 2649 | # 2650 | # Process the contents of a single definition list, splitting it 2651 | # into individual term and definition list items. 2652 | # 2653 | $less_than_tab = $this->tab_width - 1; 2654 | 2655 | # trim trailing blank lines: 2656 | $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str); 2657 | 2658 | # Process definition terms. 2659 | $list_str = preg_replace_callback('{ 2660 | (?>\A\n?|\n\n+) # leading line 2661 | ( # definition terms = $1 2662 | [ ]{0,'.$less_than_tab.'} # leading whitespace 2663 | (?!\:[ ]|[ ]) # negative lookahead for a definition 2664 | # mark (colon) or more whitespace. 2665 | (?> \S.* \n)+? # actual term (not whitespace). 2666 | ) 2667 | (?=\n?[ ]{0,3}:[ ]) # lookahead for following line feed 2668 | # with a definition mark. 2669 | }xm', 2670 | array(&$this, '_processDefListItems_callback_dt'), $list_str); 2671 | 2672 | # Process actual definitions. 2673 | $list_str = preg_replace_callback('{ 2674 | \n(\n+)? # leading line = $1 2675 | ( # marker space = $2 2676 | [ ]{0,'.$less_than_tab.'} # whitespace before colon 2677 | \:[ ]+ # definition mark (colon) 2678 | ) 2679 | ((?s:.+?)) # definition text = $3 2680 | (?= \n+ # stop at next definition mark, 2681 | (?: # next term or end of text 2682 | [ ]{0,'.$less_than_tab.'} \:[ ] | 2683 |
    | \z 2684 | ) 2685 | ) 2686 | }xm', 2687 | array(&$this, '_processDefListItems_callback_dd'), $list_str); 2688 | 2689 | return $list_str; 2690 | } 2691 | function _processDefListItems_callback_dt($matches) { 2692 | $terms = explode("\n", trim($matches[1])); 2693 | $text = ''; 2694 | foreach ($terms as $term) { 2695 | $term = $this->runSpanGamut(trim($term)); 2696 | $text .= "\n
    " . $term . "
    "; 2697 | } 2698 | return $text . "\n"; 2699 | } 2700 | function _processDefListItems_callback_dd($matches) { 2701 | $leading_line = $matches[1]; 2702 | $marker_space = $matches[2]; 2703 | $def = $matches[3]; 2704 | 2705 | if ($leading_line || preg_match('/\n{2,}/', $def)) { 2706 | # Replace marker with the appropriate whitespace indentation 2707 | $def = str_repeat(' ', strlen($marker_space)) . $def; 2708 | $def = $this->runBlockGamut($this->outdent($def . "\n\n")); 2709 | $def = "\n". $def ."\n"; 2710 | } 2711 | else { 2712 | $def = rtrim($def); 2713 | $def = $this->runSpanGamut($this->outdent($def)); 2714 | } 2715 | 2716 | return "\n
    " . $def . "
    \n"; 2717 | } 2718 | 2719 | 2720 | function doFencedCodeBlocks($text) { 2721 | # 2722 | # Adding the fenced code block syntax to regular Markdown: 2723 | # 2724 | # ~~~ 2725 | # Code block 2726 | # ~~~ 2727 | # 2728 | $less_than_tab = $this->tab_width; 2729 | 2730 | $text = preg_replace_callback('{ 2731 | (?:\n|\A) 2732 | # 1: Opening marker 2733 | ( 2734 | ~{3,} # Marker: three tilde or more. 2735 | ) 2736 | [ ]* 2737 | (?: 2738 | \.?([-_:a-zA-Z0-9]+) # 2: standalone class name 2739 | | 2740 | '.$this->id_class_attr_catch_re.' # 3: Extra attributes 2741 | )? 2742 | [ ]* \n # Whitespace and newline following marker. 2743 | 2744 | # 4: Content 2745 | ( 2746 | (?> 2747 | (?!\1 [ ]* \n) # Not a closing marker. 2748 | .*\n+ 2749 | )+ 2750 | ) 2751 | 2752 | # Closing marker. 2753 | \1 [ ]* \n 2754 | }xm', 2755 | array(&$this, '_doFencedCodeBlocks_callback'), $text); 2756 | 2757 | return $text; 2758 | } 2759 | function _doFencedCodeBlocks_callback($matches) { 2760 | $classname =& $matches[2]; 2761 | $attrs =& $matches[3]; 2762 | $codeblock = $matches[4]; 2763 | $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES); 2764 | $codeblock = preg_replace_callback('/^\n+/', 2765 | array(&$this, '_doFencedCodeBlocks_newlines'), $codeblock); 2766 | 2767 | if ($classname != "") { 2768 | if ($classname{0} == '.') 2769 | $classname = substr($classname, 1); 2770 | $attr_str = ' class="'.$this->code_class_prefix.$classname.'"'; 2771 | } else { 2772 | $attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? "pre" : "code", $attrs); 2773 | } 2774 | $pre_attr_str = $this->code_attr_on_pre ? $attr_str : ''; 2775 | $code_attr_str = $this->code_attr_on_pre ? '' : $attr_str; 2776 | $codeblock = "$codeblock
    "; 2777 | 2778 | return "\n\n".$this->hashBlock($codeblock)."\n\n"; 2779 | } 2780 | function _doFencedCodeBlocks_newlines($matches) { 2781 | return str_repeat("empty_element_suffix", 2782 | strlen($matches[0])); 2783 | } 2784 | 2785 | 2786 | # 2787 | # Redefining emphasis markers so that emphasis by underscore does not 2788 | # work in the middle of a word. 2789 | # 2790 | var $em_relist = array( 2791 | '' => '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? tags 2811 | # 2812 | # Strip leading and trailing lines: 2813 | $text = preg_replace('/\A\n+|\n+\z/', '', $text); 2814 | 2815 | $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); 2816 | 2817 | # 2818 | # Wrap

    tags and unhashify HTML blocks 2819 | # 2820 | foreach ($grafs as $key => $value) { 2821 | $value = trim($this->runSpanGamut($value)); 2822 | 2823 | # Check if this should be enclosed in a paragraph. 2824 | # Clean tag hashes & block tag hashes are left alone. 2825 | $is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value); 2826 | 2827 | if ($is_p) { 2828 | $value = "

    $value

    "; 2829 | } 2830 | $grafs[$key] = $value; 2831 | } 2832 | 2833 | # Join grafs in one text, then unhash HTML tags. 2834 | $text = implode("\n\n", $grafs); 2835 | 2836 | # Finish by removing any tag hashes still present in $text. 2837 | $text = $this->unhash($text); 2838 | 2839 | return $text; 2840 | } 2841 | 2842 | 2843 | ### Footnotes 2844 | 2845 | function stripFootnotes($text) { 2846 | # 2847 | # Strips link definitions from text, stores the URLs and titles in 2848 | # hash references. 2849 | # 2850 | $less_than_tab = $this->tab_width - 1; 2851 | 2852 | # Link defs are in the form: [^id]: url "optional title" 2853 | $text = preg_replace_callback('{ 2854 | ^[ ]{0,'.$less_than_tab.'}\[\^(.+?)\][ ]?: # note_id = $1 2855 | [ ]* 2856 | \n? # maybe *one* newline 2857 | ( # text = $2 (no blank lines allowed) 2858 | (?: 2859 | .+ # actual text 2860 | | 2861 | \n # newlines but 2862 | (?!\[\^.+?\]:\s)# negative lookahead for footnote marker. 2863 | (?!\n+[ ]{0,3}\S)# ensure line is not blank and followed 2864 | # by non-indented content 2865 | )* 2866 | ) 2867 | }xm', 2868 | array(&$this, '_stripFootnotes_callback'), 2869 | $text); 2870 | return $text; 2871 | } 2872 | function _stripFootnotes_callback($matches) { 2873 | $note_id = $this->fn_id_prefix . $matches[1]; 2874 | $this->footnotes[$note_id] = $this->outdent($matches[2]); 2875 | return ''; # String that will replace the block 2876 | } 2877 | 2878 | 2879 | function doFootnotes($text) { 2880 | # 2881 | # Replace footnote references in $text [^id] with a special text-token 2882 | # which will be replaced by the actual footnote marker in appendFootnotes. 2883 | # 2884 | if (!$this->in_anchor) { 2885 | $text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text); 2886 | } 2887 | return $text; 2888 | } 2889 | 2890 | 2891 | function appendFootnotes($text) { 2892 | # 2893 | # Append footnote list to text. 2894 | # 2895 | $text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', 2896 | array(&$this, '_appendFootnotes_callback'), $text); 2897 | 2898 | if (!empty($this->footnotes_ordered)) { 2899 | $text .= "\n\n"; 2900 | $text .= "
    \n"; 2901 | $text .= "empty_element_suffix ."\n"; 2902 | $text .= "
      \n\n"; 2903 | 2904 | $attr = " rev=\"footnote\""; 2905 | if ($this->fn_backlink_class != "") { 2906 | $class = $this->fn_backlink_class; 2907 | $class = $this->encodeAttribute($class); 2908 | $attr .= " class=\"$class\""; 2909 | } 2910 | if ($this->fn_backlink_title != "") { 2911 | $title = $this->fn_backlink_title; 2912 | $title = $this->encodeAttribute($title); 2913 | $attr .= " title=\"$title\""; 2914 | } 2915 | $num = 0; 2916 | 2917 | while (!empty($this->footnotes_ordered)) { 2918 | $footnote = reset($this->footnotes_ordered); 2919 | $note_id = key($this->footnotes_ordered); 2920 | unset($this->footnotes_ordered[$note_id]); 2921 | $ref_count = $this->footnotes_ref_count[$note_id]; 2922 | unset($this->footnotes_ref_count[$note_id]); 2923 | unset($this->footnotes[$note_id]); 2924 | 2925 | $footnote .= "\n"; # Need to append newline before parsing. 2926 | $footnote = $this->runBlockGamut("$footnote\n"); 2927 | $footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', 2928 | array(&$this, '_appendFootnotes_callback'), $footnote); 2929 | 2930 | $attr = str_replace("%%", ++$num, $attr); 2931 | $note_id = $this->encodeAttribute($note_id); 2932 | 2933 | # Prepare backlink, multiple backlinks if multiple references 2934 | $backlink = ""; 2935 | for ($ref_num = 2; $ref_num <= $ref_count; ++$ref_num) { 2936 | $backlink .= " "; 2937 | } 2938 | # Add backlink to last paragraph; create new paragraph if needed. 2939 | if (preg_match('{

      $}', $footnote)) { 2940 | $footnote = substr($footnote, 0, -4) . " $backlink

      "; 2941 | } else { 2942 | $footnote .= "\n\n

      $backlink

      "; 2943 | } 2944 | 2945 | $text .= "
    1. \n"; 2946 | $text .= $footnote . "\n"; 2947 | $text .= "
    2. \n\n"; 2948 | } 2949 | 2950 | $text .= "
    \n"; 2951 | $text .= "
    "; 2952 | } 2953 | return $text; 2954 | } 2955 | function _appendFootnotes_callback($matches) { 2956 | $node_id = $this->fn_id_prefix . $matches[1]; 2957 | 2958 | # Create footnote marker only if it has a corresponding footnote *and* 2959 | # the footnote hasn't been used by another marker. 2960 | if (isset($this->footnotes[$node_id])) { 2961 | $num =& $this->footnotes_numbers[$node_id]; 2962 | if (!isset($num)) { 2963 | # Transfer footnote content to the ordered list and give it its 2964 | # number 2965 | $this->footnotes_ordered[$node_id] = $this->footnotes[$node_id]; 2966 | $this->footnotes_ref_count[$node_id] = 1; 2967 | $num = $this->footnote_counter++; 2968 | $ref_count_mark = ''; 2969 | } else { 2970 | $ref_count_mark = $this->footnotes_ref_count[$node_id] += 1; 2971 | } 2972 | 2973 | $attr = " rel=\"footnote\""; 2974 | if ($this->fn_link_class != "") { 2975 | $class = $this->fn_link_class; 2976 | $class = $this->encodeAttribute($class); 2977 | $attr .= " class=\"$class\""; 2978 | } 2979 | if ($this->fn_link_title != "") { 2980 | $title = $this->fn_link_title; 2981 | $title = $this->encodeAttribute($title); 2982 | $attr .= " title=\"$title\""; 2983 | } 2984 | 2985 | $attr = str_replace("%%", $num, $attr); 2986 | $node_id = $this->encodeAttribute($node_id); 2987 | 2988 | return 2989 | "". 2990 | "$num". 2991 | ""; 2992 | } 2993 | 2994 | return "[^".$matches[1]."]"; 2995 | } 2996 | 2997 | 2998 | ### Abbreviations ### 2999 | 3000 | function stripAbbreviations($text) { 3001 | # 3002 | # Strips abbreviations from text, stores titles in hash references. 3003 | # 3004 | $less_than_tab = $this->tab_width - 1; 3005 | 3006 | # Link defs are in the form: [id]*: url "optional title" 3007 | $text = preg_replace_callback('{ 3008 | ^[ ]{0,'.$less_than_tab.'}\*\[(.+?)\][ ]?: # abbr_id = $1 3009 | (.*) # text = $2 (no blank lines allowed) 3010 | }xm', 3011 | array(&$this, '_stripAbbreviations_callback'), 3012 | $text); 3013 | return $text; 3014 | } 3015 | function _stripAbbreviations_callback($matches) { 3016 | $abbr_word = $matches[1]; 3017 | $abbr_desc = $matches[2]; 3018 | if ($this->abbr_word_re) 3019 | $this->abbr_word_re .= '|'; 3020 | $this->abbr_word_re .= preg_quote($abbr_word); 3021 | $this->abbr_desciptions[$abbr_word] = trim($abbr_desc); 3022 | return ''; # String that will replace the block 3023 | } 3024 | 3025 | 3026 | function doAbbreviations($text) { 3027 | # 3028 | # Find defined abbreviations in text and wrap them in elements. 3029 | # 3030 | if ($this->abbr_word_re) { 3031 | // cannot use the /x modifier because abbr_word_re may 3032 | // contain significant spaces: 3033 | $text = preg_replace_callback('{'. 3034 | '(?abbr_word_re.')'. 3036 | '(?![\w\x1A])'. 3037 | '}', 3038 | array(&$this, '_doAbbreviations_callback'), $text); 3039 | } 3040 | return $text; 3041 | } 3042 | function _doAbbreviations_callback($matches) { 3043 | $abbr = $matches[0]; 3044 | if (isset($this->abbr_desciptions[$abbr])) { 3045 | $desc = $this->abbr_desciptions[$abbr]; 3046 | if (empty($desc)) { 3047 | return $this->hashPart("$abbr"); 3048 | } else { 3049 | $desc = $this->encodeAttribute($desc); 3050 | return $this->hashPart("$abbr"); 3051 | } 3052 | } else { 3053 | return $matches[0]; 3054 | } 3055 | } 3056 | 3057 | } 3058 | 3059 | 3060 | /* 3061 | 3062 | PHP Markdown Extra 3063 | ================== 3064 | 3065 | Description 3066 | ----------- 3067 | 3068 | This is a PHP port of the original Markdown formatter written in Perl 3069 | by John Gruber. This special "Extra" version of PHP Markdown features 3070 | further enhancements to the syntax for making additional constructs 3071 | such as tables and definition list. 3072 | 3073 | Markdown is a text-to-HTML filter; it translates an easy-to-read / 3074 | easy-to-write structured text format into HTML. Markdown's text format 3075 | is mostly similar to that of plain text email, and supports features such 3076 | as headers, *emphasis*, code blocks, blockquotes, and links. 3077 | 3078 | Markdown's syntax is designed not as a generic markup language, but 3079 | specifically to serve as a front-end to (X)HTML. You can use span-level 3080 | HTML tags anywhere in a Markdown document, and you can use block level 3081 | HTML tags (like
    and as well). 3082 | 3083 | For more information about Markdown's syntax, see: 3084 | 3085 | 3086 | 3087 | 3088 | Bugs 3089 | ---- 3090 | 3091 | To file bug reports please send email to: 3092 | 3093 | 3094 | 3095 | Please include with your report: (1) the example input; (2) the output you 3096 | expected; (3) the output Markdown actually produced. 3097 | 3098 | 3099 | Version History 3100 | --------------- 3101 | 3102 | See the readme file for detailed release notes for this version. 3103 | 3104 | 3105 | Copyright and License 3106 | --------------------- 3107 | 3108 | PHP Markdown & Extra 3109 | Copyright (c) 2004-2013 Michel Fortin 3110 | 3111 | All rights reserved. 3112 | 3113 | Based on Markdown 3114 | Copyright (c) 2003-2006 John Gruber 3115 | 3116 | All rights reserved. 3117 | 3118 | Redistribution and use in source and binary forms, with or without 3119 | modification, are permitted provided that the following conditions are 3120 | met: 3121 | 3122 | * Redistributions of source code must retain the above copyright notice, 3123 | this list of conditions and the following disclaimer. 3124 | 3125 | * Redistributions in binary form must reproduce the above copyright 3126 | notice, this list of conditions and the following disclaimer in the 3127 | documentation and/or other materials provided with the distribution. 3128 | 3129 | * Neither the name "Markdown" nor the names of its contributors may 3130 | be used to endorse or promote products derived from this software 3131 | without specific prior written permission. 3132 | 3133 | This software is provided by the copyright holders and contributors "as 3134 | is" and any express or implied warranties, including, but not limited 3135 | to, the implied warranties of merchantability and fitness for a 3136 | particular purpose are disclaimed. In no event shall the copyright owner 3137 | or contributors be liable for any direct, indirect, incidental, special, 3138 | exemplary, or consequential damages (including, but not limited to, 3139 | procurement of substitute goods or services; loss of use, data, or 3140 | profits; or business interruption) however caused and on any theory of 3141 | liability, whether in contract, strict liability, or tort (including 3142 | negligence or otherwise) arising in any way out of the use of this 3143 | software, even if advised of the possibility of such damage. 3144 | 3145 | */ -------------------------------------------------------------------------------- /includes/repo-group/manage-columns.php: -------------------------------------------------------------------------------- 1 | 'group_repo', 'term' => $term->slug ), admin_url( 'edit.php' ) ); 31 | $theme_url = add_query_arg( 'post_type', 'theme_repo', $url ); 32 | $plugin_url = add_query_arg( 'post_type', 'plugin_repo', $url ); 33 | ?> 34 |
    35 |

    36 | 37 | count ); ?> 38 |

    39 |

    40 | 41 | 42 |

    43 |

    44 | 45 | 46 |

    47 |
    48 | false, 24 | 'show_ui' => true, 25 | 'show_in_nav_menus' => false, 26 | 'show_tagcloud' => false, 27 | 'show_admin_column' => true, 28 | 'hierarchical' => true, 29 | 'query_var' => false, 30 | 'capabilities' => array( 31 | 'manage_terms' => 'manage_fx_updaters', 32 | 'edit_terms' => 'manage_fx_updaters', 33 | 'delete_terms' => 'manage_fx_updaters', 34 | 'assign_terms' => 'edit_fx_updaters', 35 | ), 36 | 'rewrite' => false, 37 | 'labels' => array( 38 | 'name' => _x( 'Repo Groups', 'group', 'fx-updater' ), 39 | 'singular_name' => _x( 'Repo Group', 'group', 'fx-updater' ), 40 | 'menu_name' => _x( 'Groups', 'group', 'fx-updater' ), 41 | 'name_admin_bar' => _x( 'Groups', 'group', 'fx-updater' ), 42 | 'search_items' => _x( 'Search Groups', 'group', 'fx-updater' ), 43 | 'popular_items' => _x( 'Popular Groups', 'group', 'fx-updater' ), 44 | 'all_items' => _x( 'All Groups', 'group', 'fx-updater' ), 45 | 'edit_item' => _x( 'Edit Group', 'group', 'fx-updater' ), 46 | 'view_item' => _x( 'View Group', 'group', 'fx-updater' ), 47 | 'update_item' => _x( 'Update Group', 'group', 'fx-updater' ), 48 | 'add_new_item' => _x( 'Add New Group', 'group', 'fx-updater' ), 49 | 'new_item_name' => _x( 'New Group Name', 'group', 'fx-updater' ), 50 | 'separate_items_with_commas'=> _x( 'Separate groups with commas', 'group', 'fx-updater' ), 51 | 'add_or_remove_items' => _x( 'Add or remove groups', 'group', 'fx-updater' ), 52 | 'choose_from_most_used' => _x( 'Choose from the most used groups', 'group', 'fx-updater' ), 53 | ) 54 | ); 55 | 56 | /* Register Custom Taxonomy */ 57 | $args = apply_filters( 'group_repo_taxonomy_args', $args ); 58 | register_taxonomy( 'group_repo', array( 'plugin_repo', 'theme_repo' ), $args ); 59 | } 60 | 61 | 62 | 63 | /* === ADD ADMIN MENU AS SUB MENU === */ 64 | 65 | /* Admin Menu */ 66 | add_action( 'admin_menu', 'fx_updater_group_repo_admin_menu' ); 67 | 68 | /** 69 | * Add admin menu as sub menu in f(x) Updater Settings. 70 | * @since 1.0.0 71 | * @link https://shellcreeper.com/how-to-add-wordpress-cpt-admin-menu-as-sub-menu/ 72 | */ 73 | function fx_updater_group_repo_admin_menu(){ 74 | 75 | $group_repo = get_taxonomy( 'group_repo' ); 76 | 77 | add_submenu_page( 78 | 'fx_updater', // parent slug 79 | $group_repo->labels->name, // page title 80 | $group_repo->labels->menu_name, // menu title 81 | $group_repo->cap->manage_terms, // capability (edit_fx_updaters) 82 | 'edit-tags.php?taxonomy=group_repo' // menu slug 83 | ); 84 | } 85 | 86 | /* Parent Menu Fix */ 87 | add_filter( 'parent_file', 'fx_updater_group_repo_parent_file' ); 88 | 89 | /** 90 | * Fix active parent admin menu to f(x) Updater settings 91 | * @since 1.0.0 92 | * @link https://shellcreeper.com/how-to-add-wordpress-cpt-admin-menu-as-sub-menu/ 93 | */ 94 | function fx_updater_group_repo_parent_file( $parent_file ){ 95 | global $current_screen, $self; 96 | if ( in_array( $current_screen->base, array( 'edit-tags', 'term' ) ) && 'group_repo' == $current_screen->taxonomy ) { 97 | $parent_file = 'fx_updater'; 98 | } 99 | return $parent_file; 100 | } 101 | 102 | 103 | -------------------------------------------------------------------------------- /includes/repo-group/repo-group.php: -------------------------------------------------------------------------------- 1 | '', 21 | 'title' => _x( 'Plugins', 'plugins', 'fx-updater' ), 22 | 'updater_info' => _x( 'Info', 'plugins', 'fx-updater' ), 23 | ); 24 | 25 | return array_merge( $new_columns, $columns ); 26 | } 27 | 28 | /** 29 | * Custom Columns 30 | * @since 1.0.0 31 | */ 32 | function fx_updater_plugin_custom_columns( $column, $post_id ){ 33 | switch( $column ) { 34 | case 'updater_info' : 35 | /* Vars */ 36 | $status = '' . _x( 'Active', 'plugins', 'fx-updater' ) . ''; 37 | $version = get_post_meta( $post_id, 'version', true ); 38 | if( !$version ){ 39 | $status = '' . _x( 'Not Active', 'plugins', 'fx-updater' ) . ''; 40 | $version = 'N/A'; 41 | } 42 | $package = get_post_meta( $post_id, 'download_link', true ); 43 | if( !$package ){ 44 | $package = 'N/A'; 45 | $status = '' . _x( 'Not Active', 'plugins', 'fx-updater' ) . ''; 46 | } 47 | else{ 48 | $package = '' . _x( 'Download ZIP', 'plugins', 'fx-updater' ) . ''; 49 | } 50 | $plugin_id = get_post_meta( $post_id, 'id', true ); 51 | if( !$plugin_id ){ 52 | $plugin_id = 'N/A'; 53 | $status = '' . _x( 'Not Active', 'plugins', 'fx-updater' ) . ''; 54 | } 55 | $post_status = get_post_status( $post_id ); 56 | if( 'publish' !== $post_status ){ 57 | $status = '' . _x( 'Not Active', 'plugins', 'fx-updater' ) . ''; 58 | } 59 | $wp = 'N/A'; 60 | $wp_requires = get_post_meta( $post_id, 'requires', true ); 61 | $wp_tested = get_post_meta( $post_id, 'tested', true ); 62 | if( $wp_requires && $wp_tested ){ 63 | $wp = $wp_requires . " - " . $wp_tested; 64 | } 65 | elseif( $wp_tested ){ 66 | $wp = $wp_tested; 67 | } 68 | ?> 69 |
    70 |

    71 | 72 | : 73 |

    74 |

    75 | 76 | : 77 |

    78 |

    79 | 80 | : 81 |

    82 |

    83 | 84 | : 85 |

    86 |

    87 | 88 | : 89 |

    90 |
    91 | get_taxonomy( 'group_repo' )->labels->all_items, 132 | 'hide_empty' => 0, 133 | 'hierarchical' => 1, 134 | 'show_count' => 0, 135 | 'orderby' => 'name', 136 | 'selected' => $selected, 137 | 'taxonomy' => 'group_repo', 138 | 'name' => 'term', 139 | 'value_field' => 'slug', 140 | ); 141 | 142 | echo ''; 143 | wp_dropdown_categories( $dropdown_options ); 144 | } 145 | } 146 | 147 | -------------------------------------------------------------------------------- /includes/repo-plugin/meta-box-data.php: -------------------------------------------------------------------------------- 1 | ID; 38 | 39 | /* Plugin ID */ 40 | $plugin_id = get_post_meta( $post_id, 'id', true ); 41 | 42 | /* Download ZIP */ 43 | $download_link = get_post_meta( $post_id, 'download_link', true ); 44 | 45 | /* Version */ 46 | $version = 'post-new.php' == $hook_suffix ? '1.0.0' : get_post_meta( $post_id, 'version', true ); 47 | 48 | /* Last Updated */ 49 | $last_updated = fx_updater_explode_date( get_post_meta( $post_id, 'last_updated', true ) ); 50 | $year = date( 'Y' ); 51 | $month = date( 'm' ); 52 | $day = date( 'd' ); 53 | if( $last_updated ){ 54 | $year = 'post-new.php' == $hook_suffix ? $year : $last_updated['year']; 55 | $month = 'post-new.php' == $hook_suffix ? $month : $last_updated['month']; 56 | $day = 'post-new.php' == $hook_suffix ? $day : $last_updated['day']; 57 | } 58 | 59 | /* WP Version */ 60 | $wp_requires = 'post-new.php' == $hook_suffix ? "" : get_post_meta( $post_id, 'requires', true ); 61 | $wp_tested = 'post-new.php' == $hook_suffix ? $wp_version : get_post_meta( $post_id, 'tested', true ); 62 | 63 | /* Changelog */ 64 | $changelog = get_post_meta( $post_id, 'section_changelog', true ); 65 | 66 | /* Upgrade Notice */ 67 | $upgrade_notice = strip_tags( get_post_meta( $post_id, 'upgrade_notice', true ) ); 68 | ?> 69 | 70 |
    71 | 72 |
    73 |
    74 |

    75 | 76 |

    77 |
    78 |
    79 |

    80 | 81 |

    82 |

    83 | 84 |

    85 |
    86 |
    87 | 88 |
    89 |
    90 |

    91 | 92 |

    93 |
    94 |
    95 |

    96 | 97 | 98 |

    99 |
    100 |
    101 | 102 |
    103 |
    104 |

    105 | 106 |

    107 |
    108 |
    109 |

    110 | 111 |

    112 | 113 |

    114 | 115 | 116 |

    117 |

    118 | 119 |

    120 |
    121 |
    122 | 123 |
    124 |
    125 |

    126 | 127 |

    128 |
    129 |
    130 |

    131 | 145 | , 146 | 147 |

    148 |

    149 |
    150 |
    151 | 152 |
    153 |
    154 |

    155 | 156 |

    157 |
    158 |
    159 |

    160 | 161 | 162 |

    163 |

    164 | 165 | 166 |

    167 |
    168 |
    169 | 170 |
    171 |
    172 |

    173 | 174 |

    175 |
    176 |
    177 |
    178 | 179 |
    180 |

    181 | 182 |

    183 |
    184 |
    185 | 186 |
    187 |
    188 |

    189 | 190 |

    191 |
    192 |
    193 |

    194 | 195 |

    196 |

    197 | 198 |

    199 |
    200 |
    201 | 202 |
    203 | 204 | post_type ); 231 | if ( 'plugin_repo' != $post->post_type || !current_user_can( $post_type->cap->edit_post, $post_id ) ){ 232 | return $post_id; 233 | } 234 | 235 | /* == PLUGIN ID == */ 236 | 237 | /* Get (old) saved data */ 238 | $old_data = get_post_meta( $post_id, 'id', true ); 239 | 240 | /* Get new submitted data and sanitize it. */ 241 | $new_data = isset( $request['id'] ) ? esc_attr( $request['id'] ) : ''; 242 | 243 | /* New data submitted, No previous data, create it */ 244 | if ( $new_data && '' == $old_data ){ 245 | add_post_meta( $post_id, 'id', $new_data, true ); 246 | } 247 | /* New data submitted, but it's different data than previously stored data, update it */ 248 | elseif( $new_data && ( $new_data != $old_data ) ){ 249 | update_post_meta( $post_id, 'id', $new_data ); 250 | } 251 | /* New data submitted is empty, but there's old data available, delete it. */ 252 | elseif ( empty( $new_data ) && $old_data ){ 253 | delete_post_meta( $post_id, 'id' ); 254 | } 255 | 256 | /* == ZIP FILE == */ 257 | 258 | /* Get (old) saved data */ 259 | $old_data = get_post_meta( $post_id, 'download_link', true ); 260 | 261 | /* Get new submitted data and sanitize it. */ 262 | $new_data = isset( $request['download_link'] ) ? esc_url_raw( $request['download_link'] ) : ''; 263 | 264 | /* New data submitted, No previous data, create it */ 265 | if ( $new_data && '' == $old_data ){ 266 | add_post_meta( $post_id, 'download_link', $new_data, true ); 267 | } 268 | /* New data submitted, but it's different data than previously stored data, update it */ 269 | elseif( $new_data && ( $new_data != $old_data ) ){ 270 | update_post_meta( $post_id, 'download_link', $new_data ); 271 | } 272 | /* New data submitted is empty, but there's old data available, delete it. */ 273 | elseif ( empty( $new_data ) && $old_data ){ 274 | delete_post_meta( $post_id, 'download_link' ); 275 | } 276 | 277 | /* == VERSION == */ 278 | 279 | /* Get (old) saved data */ 280 | $old_data = get_post_meta( $post_id, 'version', true ); 281 | 282 | /* Get new submitted data and sanitize it. */ 283 | $new_data = isset( $request['version'] ) ? fx_updater_sanitize_version( $request['version'] ) : ''; 284 | 285 | /* New data submitted, No previous data, create it */ 286 | if ( $new_data && '' == $old_data ){ 287 | add_post_meta( $post_id, 'version', $new_data, true ); 288 | } 289 | /* New data submitted, but it's different data than previously stored data, update it */ 290 | elseif( $new_data && ( $new_data != $old_data ) ){ 291 | update_post_meta( $post_id, 'version', $new_data ); 292 | } 293 | /* New data submitted is empty, but there's old data available, delete it. */ 294 | elseif ( empty( $new_data ) && $old_data ){ 295 | delete_post_meta( $post_id, 'version' ); 296 | } 297 | 298 | /* === RELEASE DATE === */ 299 | 300 | /* Get (old) saved data */ 301 | $old_data = get_post_meta( $post_id, 'last_updated', true ); 302 | 303 | /* Get new submitted data and sanitize it. */ 304 | $new_date = array( 305 | 'day' => isset( $request['last_updated_day'] ) ? $request['last_updated_day'] : date( 'd' ), 306 | 'month' => isset( $request['last_updated_month'] ) ? $request['last_updated_month'] : date( 'm' ), 307 | 'year' => isset( $request['last_updated_year'] ) ? $request['last_updated_year'] : date( 'Y' ), 308 | ); 309 | $new_data = fx_updater_format_date( $new_date ); // YYYY-MM-DD 310 | 311 | /* New data submitted, No previous data, create it */ 312 | if ( $new_data && '' == $old_data ){ 313 | add_post_meta( $post_id, 'last_updated', $new_data, true ); 314 | } 315 | /* New data submitted, but it's different data than previously stored data, update it */ 316 | elseif( $new_data && ( $new_data != $old_data ) ){ 317 | update_post_meta( $post_id, 'last_updated', $new_data ); 318 | } 319 | /* New data submitted is empty, but there's old data available, delete it. */ 320 | elseif ( empty( $new_data ) && $old_data ){ 321 | delete_post_meta( $post_id, 'last_updated' ); 322 | } 323 | 324 | /* === WP VERSION === */ 325 | 326 | /* WP Version: Required */ 327 | 328 | /* Get (old) saved data */ 329 | $old_data = get_post_meta( $post_id, 'requires', true ); 330 | 331 | /* Get new submitted data and sanitize it. */ 332 | $new_data = isset( $request['requires'] ) ? fx_updater_sanitize_version( $request['requires'] ) : ''; 333 | 334 | /* New data submitted, No previous data, create it */ 335 | if ( $new_data && '' == $old_data ){ 336 | add_post_meta( $post_id, 'requires', $new_data, true ); 337 | } 338 | /* New data submitted, but it's different data than previously stored data, update it */ 339 | elseif( $new_data && ( $new_data != $old_data ) ){ 340 | update_post_meta( $post_id, 'requires', $new_data ); 341 | } 342 | /* New data submitted is empty, but there's old data available, delete it. */ 343 | elseif ( empty( $new_data ) && $old_data ){ 344 | delete_post_meta( $post_id, 'requires' ); 345 | } 346 | 347 | /* WP Version: Tested */ 348 | 349 | /* Get (old) saved data */ 350 | $old_data = get_post_meta( $post_id, 'tested', true ); 351 | 352 | /* Get new submitted data and sanitize it. */ 353 | $new_data = isset( $request['tested'] ) ? fx_updater_sanitize_version( $request['tested'] ) : ''; 354 | 355 | /* New data submitted, No previous data, create it */ 356 | if ( $new_data && '' == $old_data ){ 357 | add_post_meta( $post_id, 'tested', $new_data, true ); 358 | } 359 | /* New data submitted, but it's different data than previously stored data, update it */ 360 | elseif( $new_data && ( $new_data != $old_data ) ){ 361 | update_post_meta( $post_id, 'tested', $new_data ); 362 | } 363 | /* New data submitted is empty, but there's old data available, delete it. */ 364 | elseif ( empty( $new_data ) && $old_data ){ 365 | delete_post_meta( $post_id, 'tested' ); 366 | } 367 | 368 | /* === CHANGELOG === */ 369 | 370 | /* Get (old) saved data */ 371 | $old_data = get_post_meta( $post_id, 'section_changelog', true ); 372 | 373 | /* Get new submitted data and sanitize it. */ 374 | $new_data = isset( $request['section_changelog'] ) ? fx_updater_sanitize_plugin_section( $request['section_changelog'] ) : ''; 375 | 376 | /* New data submitted, No previous data, create it */ 377 | if ( $new_data && '' == $old_data ){ 378 | add_post_meta( $post_id, 'section_changelog', $new_data, true ); 379 | } 380 | /* New data submitted, but it's different data than previously stored data, update it */ 381 | elseif( $new_data && ( $new_data != $old_data ) ){ 382 | update_post_meta( $post_id, 'section_changelog', $new_data ); 383 | } 384 | /* New data submitted is empty, but there's old data available, delete it. */ 385 | elseif ( empty( $new_data ) && $old_data ){ 386 | delete_post_meta( $post_id, 'section_changelog' ); 387 | } 388 | 389 | /* == UPGRADE NOTICE == */ 390 | 391 | /* Get (old) saved data */ 392 | $old_data = get_post_meta( $post_id, 'upgrade_notice', true ); 393 | 394 | /* Get new submitted data and sanitize it. */ 395 | $new_data = isset( $request['upgrade_notice'] ) ? strip_tags( $request['upgrade_notice'] ) : ''; 396 | 397 | /* New data submitted, No previous data, create it */ 398 | if ( $new_data && '' == $old_data ){ 399 | add_post_meta( $post_id, 'upgrade_notice', $new_data, true ); 400 | } 401 | /* New data submitted, but it's different data than previously stored data, update it */ 402 | elseif( $new_data && ( $new_data != $old_data ) ){ 403 | update_post_meta( $post_id, 'upgrade_notice', $new_data ); 404 | } 405 | /* New data submitted is empty, but there's old data available, delete it. */ 406 | elseif ( empty( $new_data ) && $old_data ){ 407 | delete_post_meta( $post_id, 'upgrade_notice' ); 408 | } 409 | 410 | } 411 | 412 | -------------------------------------------------------------------------------- /includes/repo-plugin/register-post-type.php: -------------------------------------------------------------------------------- 1 | '', 29 | 'public' => false, 30 | 'publicly_queryable' => false, 31 | 'show_in_nav_menus' => false, 32 | 'show_in_admin_bar' => false, 33 | 'exclude_from_search' => true, 34 | 'show_ui' => true, 35 | 'show_in_menu' => false, 36 | 'menu_position' => null, 37 | 'menu_icon' => 'dashicons-update', 38 | 'can_export' => true, 39 | 'delete_with_user' => false, 40 | 'hierarchical' => false, 41 | 'has_archive' => false, 42 | 'query_var' => false, 43 | 'rewrite' => false, 44 | 'capability_type' => 'plugin_repo', 45 | 'map_meta_cap' => true, 46 | 'capabilities' => array( 47 | 'edit_post' => 'edit_fx_updater', // don't assign these to roles 48 | 'read_post' => 'read_fx_updater', // don't assign these to roles 49 | 'delete_post' => 'delete_fx_updater', // don't assign these to roles 50 | 'create_posts' => 'create_fx_updaters', // primitive meta caps 51 | 'edit_posts' => 'edit_fx_updaters', // primitive caps outside map_meta_cap() 52 | 'edit_others_posts' => 'manage_fx_updaters', // primitive caps outside map_meta_cap() 53 | 'publish_posts' => 'manage_fx_updaters', // primitive caps outside map_meta_cap() 54 | 'read_private_posts' => 'read', 55 | 'read' => 'read', 56 | 'delete_posts' => 'manage_fx_updaters', // primitive caps inside map_meta_cap() 57 | 'delete_private_posts' => 'manage_fx_updaters', // primitive caps inside map_meta_cap() 58 | 'delete_published_posts' => 'manage_fx_updaters', // primitive caps inside map_meta_cap() 59 | 'delete_others_posts' => 'manage_fx_updaters', // primitive caps inside map_meta_cap() 60 | 'edit_private_posts' => 'edit_fx_updaters', // primitive caps inside map_meta_cap() 61 | 'edit_published_posts' => 'edit_fx_updaters' // primitive caps inside map_meta_cap() 62 | ), 63 | 'supports' => array( 'title' ), 64 | 'labels' => array( 65 | 'name' => _x( 'Plugins Updater', 'plugins', 'fx-updater' ), 66 | 'singular_name' => _x( 'Plugin Repo', 'plugins', 'fx-updater' ), 67 | 'add_new' => _x( 'Add New', 'plugins', 'fx-updater' ), 68 | 'add_new_item' => _x( 'Add New Plugin Repo', 'plugins', 'fx-updater' ), 69 | 'edit_item' => _x( 'Edit Plugin Repo', 'plugins', 'fx-updater' ), 70 | 'new_item' => _x( 'New Plugin Repo', 'plugins', 'fx-updater' ), 71 | 'all_items' => _x( 'All Plugins', 'plugins', 'fx-updater' ), 72 | 'view_item' => _x( 'View Plugin', 'plugins', 'fx-updater' ), 73 | 'search_items' => _x( 'Search Plugin', 'plugins', 'fx-updater' ), 74 | 'not_found' => _x( 'No Plugin found', 'plugins', 'fx-updater' ), 75 | 'not_found_in_trash' => _x( 'No Plugin found in Trash', 'plugins', 'fx-updater' ), 76 | 'menu_name' => _x( 'Plugins', 'plugins', 'fx-updater' ), 77 | ), 78 | ); 79 | 80 | /* REGISTER "plugin_repo" POST TYPE */ 81 | $args = apply_filters( 'plugin_repo_post_type_args', $args ); 82 | register_post_type( 'plugin_repo', $args ); 83 | } 84 | 85 | 86 | /* === ADD ADMIN MENU AS SUB MENU === */ 87 | 88 | /* Admin Menu */ 89 | add_action( 'admin_menu', 'fx_updater_plugin_repo_admin_menu' ); 90 | 91 | /** 92 | * Add admin menu as sub menu in f(x) Updater Settings. 93 | * @since 1.0.0 94 | * @link https://shellcreeper.com/how-to-add-wordpress-cpt-admin-menu-as-sub-menu/ 95 | */ 96 | function fx_updater_plugin_repo_admin_menu(){ 97 | 98 | /* Add Submenu Page: Plugin Repo */ 99 | $plugin_repo = get_post_type_object( 'plugin_repo' ); 100 | add_submenu_page( 101 | 'fx_updater', // parent slug 102 | $plugin_repo->labels->name, // page title 103 | $plugin_repo->labels->menu_name, // menu title 104 | $plugin_repo->cap->edit_posts, // capability (edit_fx_updaters) 105 | 'edit.php?post_type=plugin_repo' // menu slug 106 | ); 107 | 108 | } 109 | 110 | /* Parent Menu Fix */ 111 | add_filter( 'parent_file', 'fx_updater_plugin_repo_parent_file' ); 112 | 113 | /** 114 | * Fix active parent admin menu to f(x) Updater settings 115 | * @since 1.0.0 116 | * @link https://shellcreeper.com/how-to-add-wordpress-cpt-admin-menu-as-sub-menu/ 117 | */ 118 | function fx_updater_plugin_repo_parent_file( $parent_file ){ 119 | global $current_screen, $self; 120 | if ( in_array( $current_screen->base, array( 'post', 'edit' ) ) && 'plugin_repo' == $current_screen->post_type ) { 121 | $parent_file = 'fx_updater'; 122 | } 123 | return $parent_file; 124 | } 125 | 126 | 127 | /* === EDIT POST: TITLE PLACEHOLDER === */ 128 | 129 | /* Title Placeholder */ 130 | add_filter( 'enter_title_here', 'fx_updater_plugin_edit_title_placeholder', 10, 2 ); 131 | 132 | /** 133 | * Change "Enter title here" to "Plugin Name" 134 | * @since 0.1.0 135 | */ 136 | function fx_updater_plugin_edit_title_placeholder( $placeholder, $post ){ 137 | if( 'plugin_repo' == get_post_type( $post ) ){ 138 | $placeholder = _x( 'Plugin Name', 'plugins', 'fx-updater' ); 139 | } 140 | return $placeholder; 141 | } 142 | 143 | 144 | /* === EDIT POST: UPDATED MESSAGE === */ 145 | 146 | /* Updated message */ 147 | add_filter( 'post_updated_messages', 'fx_updater_plugin_updated_message' ); 148 | 149 | /** 150 | * Custom Updated Message 151 | * @since 0.1.0 152 | */ 153 | function fx_updater_plugin_updated_message( $messages ){ 154 | global $post, $post_ID; 155 | 156 | $messages['plugin_repo'] = array( 157 | 0 => '', // Unused. Messages start at index 1. 158 | 1 => _x( 'Plugin updated.', 'plugins', 'fx-updater' ), 159 | 2 => _x( 'Plugin field updated.', 'plugins', 'fx-updater' ), 160 | 3 => _x( 'Plugin field deleted.', 'plugins', 'fx-updater' ), 161 | 4 => _x( 'Plugin updated.', 'plugins', 'fx-updater' ), 162 | /* translators: %s: date and time of the revision */ 163 | 5 => isset($_GET['revision']) ? sprintf( _x( 'Plugin restored to revision from %s', 'plugins', 'fx-updater' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false, 164 | 6 => _x( 'Plugin published.', 'plugins', 'fx-updater' ), 165 | 7 => _x( 'Plugin saved.', 'plugins', 'fx-updater' ), 166 | 8 => _x( 'Plugin submitted.', 'plugins', 'fx-updater' ), 167 | 9 => sprintf( _x( 'Plugin scheduled for: %1$s.', 'plugins', 'fx-updater' ), 168 | /* translators: Publish box date format, see http://php.net/date */ 169 | date_i18n( _x( 'M j, Y @ H:i', 'plugins', 'fx-updater' ), strtotime( $post->post_date ) ) ), 170 | 10 => _x( 'Plugin draft updated.', 'plugins', 'fx-updater' ), 171 | ); 172 | 173 | return $messages; 174 | } 175 | 176 | -------------------------------------------------------------------------------- /includes/repo-plugin/repo-plugin.php: -------------------------------------------------------------------------------- 1 | '', 20 | 'title' => _x( 'Themes', 'plugins', 'fx-updater' ), 21 | 'updater_info' => _x( 'Info', 'plugins', 'fx-updater' ), 22 | ); 23 | $columns['updater_info'] = _x( 'Info', 'plugins', 'fx-updater' ); 24 | 25 | return array_merge( $new_columns, $columns ); 26 | } 27 | 28 | /** 29 | * Custom Columns 30 | * @since 1.0.0 31 | */ 32 | function fx_updater_theme_custom_columns( $column, $post_id ){ 33 | switch( $column ) { 34 | case 'updater_info' : 35 | /* Vars */ 36 | $status = '' . _x( 'Active', 'themes', 'fx-updater' ) . ''; 37 | $version = get_post_meta( $post_id, 'version', true ); 38 | if( !$version ){ 39 | $version = 'N/A'; 40 | $status = '' . _x( 'Not Active', 'themes', 'fx-updater' ) . ''; 41 | } 42 | $package = get_post_meta( $post_id, 'download_link', true ); 43 | if( !$package ){ 44 | $package = 'N/A'; 45 | $status = '' . _x( 'Not Active', 'themes', 'fx-updater' ) . ''; 46 | } 47 | else{ 48 | $package = '' . _x( 'Download ZIP', 'themes', 'fx-updater' ) . ''; 49 | } 50 | $theme_id = get_post_meta( $post_id, 'id', true ); 51 | if( !$theme_id ){ 52 | $theme_id = 'N/A'; 53 | $status = '' . _x( 'Not Active', 'themes', 'fx-updater' ) . ''; 54 | } 55 | $post_status = get_post_status( $post_id ); 56 | if( 'publish' !== $post_status ){ 57 | $status = '' . _x( 'Not Active', 'themes', 'fx-updater' ) . ''; 58 | } 59 | ?> 60 |
    61 |

    62 | 63 | : 64 |

    65 |

    66 | 67 | : 68 |

    69 |

    70 | 71 | : 72 |

    73 |

    74 | 75 | : 76 |

    77 |
    78 | get_taxonomy( 'group_repo' )->labels->all_items, 118 | 'hide_empty' => 0, 119 | 'hierarchical' => 1, 120 | 'show_count' => 0, 121 | 'orderby' => 'name', 122 | 'selected' => $selected, 123 | 'taxonomy' => 'group_repo', 124 | 'name' => 'term', 125 | 'value_field' => 'slug', 126 | ); 127 | 128 | echo ''; 129 | wp_dropdown_categories( $dropdown_options ); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /includes/repo-theme/meta-box-data.php: -------------------------------------------------------------------------------- 1 | ID; 38 | 39 | /* Theme ID */ 40 | $theme_id = get_post_meta( $post_id, 'id', true ); 41 | 42 | /* Download ZIP */ 43 | $download_link = get_post_meta( $post_id, 'download_link', true ); 44 | 45 | /* Version */ 46 | $version = 'post-new.php' == $hook_suffix ? '1.0.0' : get_post_meta( $post_id, 'version', true ); 47 | 48 | /* Theme type */ 49 | $theme_type = fx_updater_sanitize_theme_type( get_post_meta( $post_id, 'theme_type', true ) ); 50 | ?> 51 | 52 |
    53 | 54 |
    55 |
    56 |

    57 | 58 |

    59 |
    60 |
    61 |

    62 | 63 |

    64 |

    65 | 66 |

    67 |
    68 |
    69 | 70 |
    71 |
    72 |

    73 | 74 |

    75 |
    76 |
    77 |

    78 | 79 | 80 |

    81 |
    82 |
    83 | 84 |
    85 |
    86 |

    87 | 88 |

    89 |
    90 |
    91 |

    92 | 93 |

    94 | 95 |

    96 | 97 | 98 |

    99 |

    100 | 101 |

    102 |
    103 |
    104 | 105 |
    106 | 107 | post_type ); 134 | if ( 'theme_repo' != $post->post_type || !current_user_can( $post_type->cap->edit_post, $post_id ) ){ 135 | return $post_id; 136 | } 137 | 138 | /* == THEME ID == */ 139 | 140 | /* Get (old) saved data */ 141 | $old_data = get_post_meta( $post_id, 'id', true ); 142 | 143 | /* Get new submitted data and sanitize it. */ 144 | $new_data = isset( $request['id'] ) ? esc_attr( $request['id'] ) : ''; 145 | 146 | /* New data submitted, No previous data, create it */ 147 | if ( $new_data && '' == $old_data ){ 148 | add_post_meta( $post_id, 'id', $new_data, true ); 149 | } 150 | /* New data submitted, but it's different data than previously stored data, update it */ 151 | elseif( $new_data && ( $new_data != $old_data ) ){ 152 | update_post_meta( $post_id, 'id', $new_data ); 153 | } 154 | /* New data submitted is empty, but there's old data available, delete it. */ 155 | elseif ( empty( $new_data ) && $old_data ){ 156 | delete_post_meta( $post_id, 'id' ); 157 | } 158 | 159 | /* == ZIP FILE == */ 160 | 161 | /* Get (old) saved data */ 162 | $old_data = get_post_meta( $post_id, 'download_link', true ); 163 | 164 | /* Get new submitted data and sanitize it. */ 165 | $new_data = isset( $request['download_link'] ) ? esc_url_raw( $request['download_link'] ) : ''; 166 | 167 | /* New data submitted, No previous data, create it */ 168 | if ( $new_data && '' == $old_data ){ 169 | add_post_meta( $post_id, 'download_link', $new_data, true ); 170 | } 171 | /* New data submitted, but it's different data than previously stored data, update it */ 172 | elseif( $new_data && ( $new_data != $old_data ) ){ 173 | update_post_meta( $post_id, 'download_link', $new_data ); 174 | } 175 | /* New data submitted is empty, but there's old data available, delete it. */ 176 | elseif ( empty( $new_data ) && $old_data ){ 177 | delete_post_meta( $post_id, 'download_link' ); 178 | } 179 | 180 | /* == VERSION == */ 181 | 182 | /* Get (old) saved data */ 183 | $old_data = get_post_meta( $post_id, 'version', true ); 184 | 185 | /* Get new submitted data and sanitize it. */ 186 | $new_data = isset( $request['version'] ) ? fx_updater_sanitize_version( $request['version'] ) : ''; 187 | 188 | /* New data submitted, No previous data, create it */ 189 | if ( $new_data && '' == $old_data ){ 190 | add_post_meta( $post_id, 'version', $new_data, true ); 191 | } 192 | /* New data submitted, but it's different data than previously stored data, update it */ 193 | elseif( $new_data && ( $new_data != $old_data ) ){ 194 | update_post_meta( $post_id, 'version', $new_data ); 195 | } 196 | /* New data submitted is empty, but there's old data available, delete it. */ 197 | elseif ( empty( $new_data ) && $old_data ){ 198 | delete_post_meta( $post_id, 'version' ); 199 | } 200 | } 201 | 202 | -------------------------------------------------------------------------------- /includes/repo-theme/register-post-type.php: -------------------------------------------------------------------------------- 1 | '', 30 | 'public' => false, 31 | 'publicly_queryable' => false, 32 | 'show_in_nav_menus' => false, 33 | 'show_in_admin_bar' => false, 34 | 'exclude_from_search' => true, 35 | 'show_ui' => true, 36 | 'show_in_menu' => false, 37 | 'menu_position' => null, 38 | 'menu_icon' => 'dashicons-update', 39 | 'can_export' => true, 40 | 'delete_with_user' => false, 41 | 'hierarchical' => false, 42 | 'has_archive' => false, 43 | 'query_var' => false, 44 | 'rewrite' => false, 45 | 'capability_type' => 'theme_repo', 46 | 'map_meta_cap' => true, 47 | 'capabilities' => array( 48 | 'edit_post' => 'edit_fx_updater', // don't assign these to roles 49 | 'read_post' => 'read_fx_updater', // don't assign these to roles 50 | 'delete_post' => 'delete_fx_updater', // don't assign these to roles 51 | 'create_posts' => 'create_fx_updaters', // primitive meta caps 52 | 'edit_posts' => 'edit_fx_updaters', // primitive caps outside map_meta_cap() 53 | 'edit_others_posts' => 'manage_fx_updaters', // primitive caps outside map_meta_cap() 54 | 'publish_posts' => 'manage_fx_updaters', // primitive caps outside map_meta_cap() 55 | 'read_private_posts' => 'read', 56 | 'read' => 'read', 57 | 'delete_posts' => 'manage_fx_updaters', // primitive caps inside map_meta_cap() 58 | 'delete_private_posts' => 'manage_fx_updaters', // primitive caps inside map_meta_cap() 59 | 'delete_published_posts' => 'manage_fx_updaters', // primitive caps inside map_meta_cap() 60 | 'delete_others_posts' => 'manage_fx_updaters', // primitive caps inside map_meta_cap() 61 | 'edit_private_posts' => 'edit_fx_updaters', // primitive caps inside map_meta_cap() 62 | 'edit_published_posts' => 'edit_fx_updaters' // primitive caps inside map_meta_cap() 63 | ), 64 | 'supports' => array( 'title' ), 65 | 'labels' => array( 66 | 'name' => _x( 'Themes Repository', 'themes', 'fx-updater' ), 67 | 'singular_name' => _x( 'Theme', 'themes', 'fx-updater' ), 68 | 'add_new' => _x( 'Add New', 'themes', 'fx-updater' ), 69 | 'add_new_item' => _x( 'Add New Theme Repo', 'themes', 'fx-updater' ), 70 | 'edit_item' => _x( 'Edit Theme', 'themes', 'fx-updater' ), 71 | 'new_item' => _x( 'New Theme', 'themes', 'fx-updater' ), 72 | 'all_items' => _x( 'All Themes', 'themes', 'fx-updater' ), 73 | 'view_item' => _x( 'View Theme', 'themes', 'fx-updater' ), 74 | 'search_items' => _x( 'Search Theme', 'themes', 'fx-updater' ), 75 | 'not_found' => _x( 'No Theme Found', 'themes', 'fx-updater' ), 76 | 'not_found_in_trash' => _x( 'No Theme Found in Trash', 'themes', 'fx-updater' ), 77 | 'menu_name' => _x( 'Themes', 'themes', 'fx-updater' ), 78 | ), 79 | ); 80 | 81 | /* Register "theme_repo" post type */ 82 | $args = apply_filters( 'theme_repo_post_type_args', $args ); 83 | register_post_type( 'theme_repo', $args ); 84 | } 85 | 86 | 87 | /* === ADD ADMIN MENU AS SUB MENU === */ 88 | 89 | /* Admin Menu */ 90 | add_action( 'admin_menu', 'fx_updater_theme_admin_menu' ); 91 | 92 | /** 93 | * Add admin menu, 94 | * Submenu in fx updater settings. 95 | * @since 0.1.0 96 | */ 97 | function fx_updater_theme_admin_menu(){ 98 | 99 | $cpt_obj = get_post_type_object( 'theme_repo' ); 100 | add_submenu_page( 101 | 'fx_updater', // parent slug 102 | $cpt_obj->labels->name, // page title 103 | $cpt_obj->labels->menu_name, // menu title 104 | $cpt_obj->cap->edit_posts, // capability 105 | 'edit.php?post_type=theme_repo' // menu slug 106 | ); 107 | 108 | } 109 | 110 | /* Parent Menu Fix */ 111 | add_filter( 'parent_file', 'fx_updater_theme_parent_file' ); 112 | 113 | /** 114 | * Fix Parent Admin Menu to point to f(x) Updater settings 115 | * @since 0.1.0 116 | */ 117 | function fx_updater_theme_parent_file( $parent_file ){ 118 | global $current_screen, $self; 119 | if ( in_array( $current_screen->base, array( 'post', 'edit' ) ) && 'theme_repo' == $current_screen->post_type ) { 120 | $parent_file = 'fx_updater'; 121 | } 122 | return $parent_file; 123 | } 124 | 125 | /* === EDIT POST: TITLE PLACEHOLDER === */ 126 | 127 | /* Title Placeholder */ 128 | add_filter( 'enter_title_here', 'fx_updater_theme_edit_title_placeholder', 10, 2 ); 129 | 130 | /** 131 | * Change "Enter title here" to "Plugin Name" 132 | * @since 0.1.0 133 | */ 134 | function fx_updater_theme_edit_title_placeholder( $placeholder, $post ){ 135 | if( 'theme_repo' == get_post_type( $post ) ){ 136 | $placeholder = _x( 'Theme Name', 'themes', 'fx-updater' ); 137 | } 138 | return $placeholder; 139 | } 140 | 141 | 142 | /* === EDIT POST: UPDATED MESSAGE === */ 143 | 144 | /* Updated message */ 145 | add_filter( 'post_updated_messages', 'fx_updater_theme_updated_message' ); 146 | 147 | /** 148 | * Custom Updated Message 149 | * @since 0.1.0 150 | */ 151 | function fx_updater_theme_updated_message( $messages ){ 152 | global $post, $post_ID; 153 | 154 | $messages['theme_repo'] = array( 155 | 0 => '', // Unused. Messages start at index 1. 156 | 1 => _x( 'Theme updated.', 'themes', 'fx-updater' ), 157 | 2 => _x( 'Theme field updated.', 'themes', 'fx-updater' ), 158 | 3 => _x( 'Theme field deleted.', 'themes', 'fx-updater' ), 159 | 4 => _x( 'Theme updated.', 'themes', 'fx-updater' ), 160 | /* translators: %s: date and time of the revision */ 161 | 5 => isset($_GET['revision']) ? sprintf( _x( 'Theme restored to revision from %s', 'themes', 'fx-updater' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false, 162 | 6 => _x( 'Theme published.', 'themes', 'fx-updater' ), 163 | 7 => _x( 'Theme saved.', 'themes', 'fx-updater' ), 164 | 8 => _x( 'Theme submitted.', 'themes', 'fx-updater' ), 165 | 9 => sprintf( _x( 'Theme scheduled for: %1$s.', 'themes', 'fx-updater' ), 166 | /* translators: Publish box date format, see http://php.net/date */ 167 | date_i18n( _x( 'M j, Y @ H:i', 'themes', 'fx-updater' ), strtotime( $post->post_date ) ) ), 168 | 10 => _x( 'Theme draft updated.', 'themes', 'fx-updater' ), 169 | ); 170 | 171 | return $messages; 172 | } 173 | 174 | -------------------------------------------------------------------------------- /includes/repo-theme/repo-theme.php: -------------------------------------------------------------------------------- 1 | config = array( 17 | 'server' => 'http://genbumedia.com/', 18 | 'type' => 'plugin', 19 | 'id' => 'fx-updater/fx-updater.php', 20 | 'api' => '1.0.0', 21 | 'post' => array(), 22 | ); 23 | 24 | /* Admin Init */ 25 | add_action( 'admin_init', array( $this, 'admin_init' ) ); 26 | 27 | /* Fix Install Folder */ 28 | add_filter( 'upgrader_post_install', array( $this, 'fix_install_folder' ), 11, 3 ); 29 | } 30 | 31 | /** 32 | * Admin Init. 33 | * Some functions only available in admin. 34 | */ 35 | public function admin_init(){ 36 | 37 | /* Add theme update data */ 38 | if( 'plugin' !== $this->config['type'] ){ 39 | add_filter( 'pre_set_site_transient_update_themes', array( $this, 'add_theme_update_data' ), 10, 2 ); 40 | } 41 | 42 | /* Add plugin update data */ 43 | if( 'theme' !== $this->config['type'] ){ 44 | add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'add_plugin_update_data' ), 10, 2 ); 45 | } 46 | 47 | /* Plugin Information */ 48 | if( 'theme' !== $this->config['type'] ){ 49 | add_filter( 'plugins_api_result', array( $this, 'plugin_info' ), 10, 3 ); 50 | } 51 | } 52 | 53 | /** 54 | * Add theme update data if available 55 | */ 56 | public function add_theme_update_data( $value, $transient ){ 57 | if( isset( $value->response ) ){ 58 | $update_data = $this->get_data( 'query_themes' ); 59 | foreach( $update_data as $theme => $data ){ 60 | if( isset( $data['new_version'], $data['theme'], $data['url'] ) ){ 61 | $value->response[$theme] = (array)$data; 62 | } 63 | else{ 64 | unset( $value->response[$theme] ); 65 | } 66 | } 67 | } 68 | return $value; 69 | } 70 | 71 | /** 72 | * Add plugin update data if available 73 | */ 74 | public function add_plugin_update_data( $value, $transient ){ 75 | if( isset( $value->response ) ){ 76 | $update_data = $this->get_data( 'query_plugins' ); 77 | foreach( $update_data as $plugin => $data ){ 78 | if( isset( $data['new_version'], $data['slug'], $data['plugin'] ) ){ 79 | $value->response[$plugin] = (object)$data; 80 | } 81 | else{ 82 | unset( $value->response[$plugin] ); 83 | } 84 | } 85 | } 86 | return $value; 87 | } 88 | 89 | /** 90 | * Plugin Information 91 | */ 92 | public function plugin_info( $res, $action, $args ){ 93 | 94 | /* Get list plugin */ 95 | if( 'group' == $this->config['type'] ){ 96 | $list_plugins = $this->get_data( 'list_plugins' ); 97 | } 98 | else{ 99 | $slug = dirname( $this->config['id'] ); 100 | $list_plugins = array( 101 | $slug => $this->config['id'], 102 | ); 103 | } 104 | 105 | /* If in our list, add our data. */ 106 | if( 'plugin_information' == $action && isset( $args->slug ) && array_key_exists( $args->slug, $list_plugins ) ){ 107 | 108 | $info = $this->get_data( 'plugin_information', $list_plugins[$args->slug] ); 109 | 110 | if( isset( $info['name'], $info['slug'], $info['external'], $info['sections'] ) ){ 111 | $res = (object)$info; 112 | } 113 | } 114 | return $res; 115 | } 116 | 117 | /** 118 | * Get update data from server 119 | */ 120 | public function get_data( $action, $plugin = '' ){ 121 | 122 | /* Get WP Version */ 123 | global $wp_version; 124 | 125 | /* Remote Options */ 126 | $body = $this->config['post']; 127 | if( 'query_plugins' == $action ){ 128 | $body['plugins'] = get_plugins(); 129 | } 130 | elseif( 'query_themes' == $action ){ 131 | $themes = array(); 132 | $get_themes = wp_get_themes(); 133 | foreach( $get_themes as $theme ){ 134 | $stylesheet = $theme->get_stylesheet(); 135 | $themes[$stylesheet] = array( 136 | 'Name' => $theme->get( 'Name' ), 137 | 'ThemeURI' => $theme->get( 'ThemeURI' ), 138 | 'Description' => $theme->get( 'Description' ), 139 | 'Author' => $theme->get( 'Author' ), 140 | 'AuthorURI' => $theme->get( 'AuthorURI' ), 141 | 'Version' => $theme->get( 'Version' ), 142 | 'Template' => $theme->get( 'Template' ), 143 | 'Status' => $theme->get( 'Status' ), 144 | 'Tags' => $theme->get( 'Tags' ), 145 | 'TextDomain' => $theme->get( 'TextDomain' ), 146 | 'DomainPath' => $theme->get( 'DomainPath' ), 147 | ); 148 | } 149 | $body['themes'] = $themes; 150 | } 151 | elseif( 'plugin_information' == $action ){ 152 | $body['plugin'] = $plugin; 153 | } 154 | $options = array( 155 | 'timeout' => 20, 156 | 'body' => $body, 157 | 'user-agent' => 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ), 158 | ); 159 | 160 | /* Remote URL */ 161 | $url_args = array( 162 | 'fx_updater' => $action, 163 | $this->config['type'] => $this->config['id'], 164 | ); 165 | $server = set_url_scheme( $this->config['server'], 'http' ); 166 | $url = $http_url = add_query_arg( $url_args, $server ); 167 | if ( $ssl = wp_http_supports( array( 'ssl' ) ) ){ 168 | $url = set_url_scheme( $url, 'https' ); 169 | } 170 | 171 | /* Try HTTPS */ 172 | $raw_response = wp_remote_post( esc_url_raw( $url ), $options ); 173 | 174 | /* Fail, try HTTP */ 175 | if ( is_wp_error( $raw_response ) ) { 176 | $raw_response = wp_remote_post( esc_url_raw( $http_url ), $options ); 177 | } 178 | 179 | /* Still fail, bail. */ 180 | if ( is_wp_error( $raw_response ) || 200 != wp_remote_retrieve_response_code( $raw_response ) ) { 181 | return array(); 182 | } 183 | 184 | /* return array */ 185 | $data = json_decode( trim( wp_remote_retrieve_body( $raw_response ) ), true ); 186 | return is_array( $data ) ? $data : array(); 187 | } 188 | 189 | /** 190 | * Fix Install Folder 191 | */ 192 | public function fix_install_folder( $true, $hook_extra, $result ){ 193 | if ( isset( $hook_extra['plugin'] ) ){ 194 | global $wp_filesystem; 195 | $proper_destination = trailingslashit( $result['local_destination'] ) . dirname( $hook_extra['plugin'] ); 196 | $wp_filesystem->move( $result['destination'], $proper_destination ); 197 | $result['destination'] = $proper_destination; 198 | $result['destination_name'] = dirname( $hook_extra['plugin'] ); 199 | global $hook_suffix; 200 | if( 'update.php' == $hook_suffix && isset( $_GET['action'], $_GET['plugin'] ) && 'upgrade-plugin' == $_GET['action'] && $hook_extra['plugin'] == $_GET['plugin'] ){ 201 | activate_plugin( $hook_extra['plugin'] ); 202 | } 203 | } 204 | elseif( isset( $hook_extra['theme'] ) ){ 205 | global $wp_filesystem; 206 | $proper_destination = trailingslashit( $result['local_destination'] ) . $hook_extra['theme']; 207 | $wp_filesystem->move( $result['destination'], $proper_destination ); 208 | if( get_option( 'theme_switched' ) == $hook_extra['theme'] && $result['destination_name'] == get_stylesheet() ){ 209 | wp_clean_themes_cache(); 210 | switch_theme( $hook_extra['theme'] ); 211 | } 212 | $result['destination'] = $proper_destination; 213 | $result['destination_name'] = $hook_extra['theme']; 214 | } 215 | return $true; 216 | } 217 | 218 | } 219 | -------------------------------------------------------------------------------- /languages/fx-updater.pot: -------------------------------------------------------------------------------- 1 | #, fuzzy 2 | msgid "" 3 | msgstr "" 4 | "Project-Id-Version: f(x) Updater\n" 5 | "Report-Msgid-Bugs-To: \n" 6 | "POT-Creation-Date: 2016-05-17 09:04+0700\n" 7 | "PO-Revision-Date: \n" 8 | "Last-Translator: dave \n" 9 | "Language-Team: David Chandra P. \n" 10 | "Language: en_US\n" 11 | "MIME-Version: 1.0\n" 12 | "Content-Type: text/plain; charset=UTF-8\n" 13 | "Content-Transfer-Encoding: 8bit\n" 14 | "X-Poedit-KeywordsList: _e;__;esc_attr_e;esc_attr__;esc_html_e;esc_html__;_x;" 15 | "_n;_x:1,2c;_n:1,2;_ex\n" 16 | "X-Poedit-Basepath: ..\n" 17 | "X-Generator: Poedit 1.8.7\n" 18 | "X-Poedit-SearchPath-0: .\n" 19 | 20 | #: fx-updater.php:105 21 | msgid "Get Support" 22 | msgstr "" 23 | 24 | #: fx-updater.php:175 25 | msgid "Thank you for using our plugin :)" 26 | msgstr "" 27 | 28 | #: includes/admin-scripts.php:47 29 | msgid "Upload Theme ZIP" 30 | msgstr "" 31 | 32 | #: includes/admin-scripts.php:48 33 | msgid "Insert ZIP File" 34 | msgstr "" 35 | 36 | #: includes/get-code/settings.php:21 includes/get-code/settings.php:33 37 | #: includes/get-code/settings.php:34 38 | msgctxt "settings" 39 | msgid "Get Code" 40 | msgstr "" 41 | 42 | #: includes/get-code/settings.php:22 43 | msgctxt "settings" 44 | msgid "f(x) Updater" 45 | msgstr "" 46 | 47 | #: includes/get-code/settings.php:52 48 | msgid "f(x) Updater" 49 | msgstr "" 50 | 51 | #: includes/repo-group/manage-columns.php:18 52 | msgctxt "group" 53 | msgid "Items" 54 | msgstr "" 55 | 56 | #: includes/repo-group/manage-columns.php:37 57 | #, php-format 58 | msgctxt "group" 59 | msgid "%d Items" 60 | msgstr "" 61 | 62 | #: includes/repo-group/manage-columns.php:41 63 | msgid "View Themes" 64 | msgstr "" 65 | 66 | #: includes/repo-group/manage-columns.php:45 67 | msgid "View Plugins" 68 | msgstr "" 69 | 70 | #: includes/repo-group/register-taxonomy.php:38 71 | msgctxt "group" 72 | msgid "Repo Groups" 73 | msgstr "" 74 | 75 | #: includes/repo-group/register-taxonomy.php:39 76 | msgctxt "group" 77 | msgid "Repo Group" 78 | msgstr "" 79 | 80 | #: includes/repo-group/register-taxonomy.php:40 81 | #: includes/repo-group/register-taxonomy.php:41 82 | msgctxt "group" 83 | msgid "Groups" 84 | msgstr "" 85 | 86 | #: includes/repo-group/register-taxonomy.php:42 87 | msgctxt "group" 88 | msgid "Search Groups" 89 | msgstr "" 90 | 91 | #: includes/repo-group/register-taxonomy.php:43 92 | msgctxt "group" 93 | msgid "Popular Groups" 94 | msgstr "" 95 | 96 | #: includes/repo-group/register-taxonomy.php:44 97 | msgctxt "group" 98 | msgid "All Groups" 99 | msgstr "" 100 | 101 | #: includes/repo-group/register-taxonomy.php:45 102 | msgctxt "group" 103 | msgid "Edit Group" 104 | msgstr "" 105 | 106 | #: includes/repo-group/register-taxonomy.php:46 107 | msgctxt "group" 108 | msgid "View Group" 109 | msgstr "" 110 | 111 | #: includes/repo-group/register-taxonomy.php:47 112 | msgctxt "group" 113 | msgid "Update Group" 114 | msgstr "" 115 | 116 | #: includes/repo-group/register-taxonomy.php:48 117 | msgctxt "group" 118 | msgid "Add New Group" 119 | msgstr "" 120 | 121 | #: includes/repo-group/register-taxonomy.php:49 122 | msgctxt "group" 123 | msgid "New Group Name" 124 | msgstr "" 125 | 126 | #: includes/repo-group/register-taxonomy.php:50 127 | msgctxt "group" 128 | msgid "Separate groups with commas" 129 | msgstr "" 130 | 131 | #: includes/repo-group/register-taxonomy.php:51 132 | msgctxt "group" 133 | msgid "Add or remove groups" 134 | msgstr "" 135 | 136 | #: includes/repo-group/register-taxonomy.php:52 137 | msgctxt "group" 138 | msgid "Choose from the most used groups" 139 | msgstr "" 140 | 141 | #: includes/repo-plugin/manage-columns.php:21 142 | #: includes/repo-plugin/register-post-type.php:76 143 | msgctxt "plugins" 144 | msgid "Plugins" 145 | msgstr "" 146 | 147 | #: includes/repo-plugin/manage-columns.php:22 148 | #: includes/repo-theme/manage-columns.php:21 149 | #: includes/repo-theme/manage-columns.php:23 150 | msgctxt "plugins" 151 | msgid "Info" 152 | msgstr "" 153 | 154 | #: includes/repo-plugin/manage-columns.php:36 155 | msgctxt "plugins" 156 | msgid "Active" 157 | msgstr "" 158 | 159 | #: includes/repo-plugin/manage-columns.php:39 160 | #: includes/repo-plugin/manage-columns.php:45 161 | #: includes/repo-plugin/manage-columns.php:53 162 | #: includes/repo-plugin/manage-columns.php:57 163 | msgctxt "plugins" 164 | msgid "Not Active" 165 | msgstr "" 166 | 167 | #: includes/repo-plugin/manage-columns.php:48 168 | msgctxt "plugins" 169 | msgid "Download ZIP" 170 | msgstr "" 171 | 172 | #: includes/repo-plugin/manage-columns.php:72 173 | #: includes/repo-theme/manage-columns.php:63 174 | msgid "Status" 175 | msgstr "" 176 | 177 | #: includes/repo-plugin/manage-columns.php:76 178 | #: includes/repo-plugin/meta-box-data.php:75 179 | msgid "Plugin ID" 180 | msgstr "" 181 | 182 | #: includes/repo-plugin/manage-columns.php:80 183 | #: includes/repo-plugin/meta-box-data.php:91 184 | #: includes/repo-theme/manage-columns.php:71 185 | #: includes/repo-theme/meta-box-data.php:73 186 | msgid "Version" 187 | msgstr "" 188 | 189 | #: includes/repo-plugin/manage-columns.php:84 190 | #: includes/repo-theme/manage-columns.php:75 191 | msgid "Package" 192 | msgstr "" 193 | 194 | #: includes/repo-plugin/manage-columns.php:88 195 | #: includes/repo-plugin/meta-box-data.php:155 196 | msgid "WP Version" 197 | msgstr "" 198 | 199 | #: includes/repo-plugin/meta-box-data.php:19 200 | msgctxt "plugins" 201 | msgid "Plugin Data" 202 | msgstr "" 203 | 204 | #: includes/repo-plugin/meta-box-data.php:83 205 | msgid "Your plugin file (required)." 206 | msgstr "" 207 | 208 | #: includes/repo-plugin/meta-box-data.php:97 209 | msgid "Latest plugin version (required)." 210 | msgstr "" 211 | 212 | #: includes/repo-plugin/meta-box-data.php:105 213 | msgid "Plugin ZIP" 214 | msgstr "" 215 | 216 | #: includes/repo-plugin/meta-box-data.php:114 217 | #: includes/repo-theme/meta-box-data.php:96 218 | msgid "Upload" 219 | msgstr "" 220 | 221 | #: includes/repo-plugin/meta-box-data.php:115 222 | #: includes/repo-theme/meta-box-data.php:97 223 | msgid "Remove" 224 | msgstr "" 225 | 226 | #: includes/repo-plugin/meta-box-data.php:118 227 | msgid "Input plugin ZIP URL or upload it." 228 | msgstr "" 229 | 230 | #: includes/repo-plugin/meta-box-data.php:126 231 | msgid "Release Date" 232 | msgstr "" 233 | 234 | #: includes/repo-plugin/meta-box-data.php:132 235 | msgid "01-Jan" 236 | msgstr "" 237 | 238 | #: includes/repo-plugin/meta-box-data.php:133 239 | msgid "02-Feb" 240 | msgstr "" 241 | 242 | #: includes/repo-plugin/meta-box-data.php:134 243 | msgid "03-Mar" 244 | msgstr "" 245 | 246 | #: includes/repo-plugin/meta-box-data.php:135 247 | msgid "04-Apr" 248 | msgstr "" 249 | 250 | #: includes/repo-plugin/meta-box-data.php:136 251 | msgid "05-May" 252 | msgstr "" 253 | 254 | #: includes/repo-plugin/meta-box-data.php:137 255 | msgid "06-Jun" 256 | msgstr "" 257 | 258 | #: includes/repo-plugin/meta-box-data.php:138 259 | msgid "07-Jul" 260 | msgstr "" 261 | 262 | #: includes/repo-plugin/meta-box-data.php:139 263 | msgid "08-Aug" 264 | msgstr "" 265 | 266 | #: includes/repo-plugin/meta-box-data.php:140 267 | msgid "09-Sep" 268 | msgstr "" 269 | 270 | #: includes/repo-plugin/meta-box-data.php:141 271 | msgid "10-Oct" 272 | msgstr "" 273 | 274 | #: includes/repo-plugin/meta-box-data.php:142 275 | msgid "11-Nov" 276 | msgstr "" 277 | 278 | #: includes/repo-plugin/meta-box-data.php:143 279 | msgid "12-Dec" 280 | msgstr "" 281 | 282 | #: includes/repo-plugin/meta-box-data.php:148 283 | msgid "Last updated date. (Month, Date, Year)" 284 | msgstr "" 285 | 286 | #: includes/repo-plugin/meta-box-data.php:161 287 | msgid "Minimum/Required WordPress version." 288 | msgstr "" 289 | 290 | #: includes/repo-plugin/meta-box-data.php:165 291 | msgid "Up to/Tested WordPress version." 292 | msgstr "" 293 | 294 | #: includes/repo-plugin/meta-box-data.php:173 295 | msgid "Changelog" 296 | msgstr "" 297 | 298 | #: includes/repo-plugin/meta-box-data.php:178 299 | msgid "Your plugin changelog here..." 300 | msgstr "" 301 | 302 | #: includes/repo-plugin/meta-box-data.php:181 303 | msgid "Add changelog using markdown or HTML." 304 | msgstr "" 305 | 306 | #: includes/repo-plugin/meta-box-data.php:189 307 | msgid "Upgrade Notice" 308 | msgstr "" 309 | 310 | #: includes/repo-plugin/meta-box-data.php:197 311 | msgid "Upgrade Notice for this version. No HTML allowed." 312 | msgstr "" 313 | 314 | #: includes/repo-plugin/register-post-type.php:65 315 | msgctxt "plugins" 316 | msgid "Plugins Updater" 317 | msgstr "" 318 | 319 | #: includes/repo-plugin/register-post-type.php:66 320 | msgctxt "plugins" 321 | msgid "Plugin Repo" 322 | msgstr "" 323 | 324 | #: includes/repo-plugin/register-post-type.php:67 325 | msgctxt "plugins" 326 | msgid "Add New" 327 | msgstr "" 328 | 329 | #: includes/repo-plugin/register-post-type.php:68 330 | msgctxt "plugins" 331 | msgid "Add New Plugin Repo" 332 | msgstr "" 333 | 334 | #: includes/repo-plugin/register-post-type.php:69 335 | msgctxt "plugins" 336 | msgid "Edit Plugin Repo" 337 | msgstr "" 338 | 339 | #: includes/repo-plugin/register-post-type.php:70 340 | msgctxt "plugins" 341 | msgid "New Plugin Repo" 342 | msgstr "" 343 | 344 | #: includes/repo-plugin/register-post-type.php:71 345 | msgctxt "plugins" 346 | msgid "All Plugins" 347 | msgstr "" 348 | 349 | #: includes/repo-plugin/register-post-type.php:72 350 | msgctxt "plugins" 351 | msgid "View Plugin" 352 | msgstr "" 353 | 354 | #: includes/repo-plugin/register-post-type.php:73 355 | msgctxt "plugins" 356 | msgid "Search Plugin" 357 | msgstr "" 358 | 359 | #: includes/repo-plugin/register-post-type.php:74 360 | msgctxt "plugins" 361 | msgid "No Plugin found" 362 | msgstr "" 363 | 364 | #: includes/repo-plugin/register-post-type.php:75 365 | msgctxt "plugins" 366 | msgid "No Plugin found in Trash" 367 | msgstr "" 368 | 369 | #: includes/repo-plugin/register-post-type.php:138 370 | msgctxt "plugins" 371 | msgid "Plugin Name" 372 | msgstr "" 373 | 374 | #: includes/repo-plugin/register-post-type.php:158 375 | #: includes/repo-plugin/register-post-type.php:161 376 | msgctxt "plugins" 377 | msgid "Plugin updated." 378 | msgstr "" 379 | 380 | #: includes/repo-plugin/register-post-type.php:159 381 | msgctxt "plugins" 382 | msgid "Plugin field updated." 383 | msgstr "" 384 | 385 | #: includes/repo-plugin/register-post-type.php:160 386 | msgctxt "plugins" 387 | msgid "Plugin field deleted." 388 | msgstr "" 389 | 390 | #: includes/repo-plugin/register-post-type.php:163 391 | #, php-format 392 | msgctxt "plugins" 393 | msgid "Plugin restored to revision from %s" 394 | msgstr "" 395 | 396 | #: includes/repo-plugin/register-post-type.php:164 397 | msgctxt "plugins" 398 | msgid "Plugin published." 399 | msgstr "" 400 | 401 | #: includes/repo-plugin/register-post-type.php:165 402 | msgctxt "plugins" 403 | msgid "Plugin saved." 404 | msgstr "" 405 | 406 | #: includes/repo-plugin/register-post-type.php:166 407 | msgctxt "plugins" 408 | msgid "Plugin submitted." 409 | msgstr "" 410 | 411 | #: includes/repo-plugin/register-post-type.php:167 412 | #, php-format 413 | msgctxt "plugins" 414 | msgid "Plugin scheduled for: %1$s." 415 | msgstr "" 416 | 417 | #: includes/repo-plugin/register-post-type.php:169 418 | msgctxt "plugins" 419 | msgid "M j, Y @ H:i" 420 | msgstr "" 421 | 422 | #: includes/repo-plugin/register-post-type.php:170 423 | msgctxt "plugins" 424 | msgid "Plugin draft updated." 425 | msgstr "" 426 | 427 | #: includes/repo-theme/manage-columns.php:20 428 | msgctxt "plugins" 429 | msgid "Themes" 430 | msgstr "" 431 | 432 | #: includes/repo-theme/manage-columns.php:36 433 | msgctxt "themes" 434 | msgid "Active" 435 | msgstr "" 436 | 437 | #: includes/repo-theme/manage-columns.php:40 438 | #: includes/repo-theme/manage-columns.php:45 439 | #: includes/repo-theme/manage-columns.php:53 440 | #: includes/repo-theme/manage-columns.php:57 441 | msgctxt "themes" 442 | msgid "Not Active" 443 | msgstr "" 444 | 445 | #: includes/repo-theme/manage-columns.php:48 446 | msgctxt "themes" 447 | msgid "Download ZIP" 448 | msgstr "" 449 | 450 | #: includes/repo-theme/manage-columns.php:67 451 | #: includes/repo-theme/meta-box-data.php:57 452 | msgid "Theme ID" 453 | msgstr "" 454 | 455 | #: includes/repo-theme/meta-box-data.php:19 456 | msgctxt "themes" 457 | msgid "Theme Data" 458 | msgstr "" 459 | 460 | #: includes/repo-theme/meta-box-data.php:65 461 | msgid "Your theme folder (required)." 462 | msgstr "" 463 | 464 | #: includes/repo-theme/meta-box-data.php:79 465 | msgid "Latest theme version (required)." 466 | msgstr "" 467 | 468 | #: includes/repo-theme/meta-box-data.php:87 469 | msgid "Theme ZIP" 470 | msgstr "" 471 | 472 | #: includes/repo-theme/meta-box-data.php:100 473 | msgid "Input theme ZIP URL or upload it." 474 | msgstr "" 475 | 476 | #: includes/repo-theme/register-post-type.php:66 477 | msgctxt "themes" 478 | msgid "Themes Repository" 479 | msgstr "" 480 | 481 | #: includes/repo-theme/register-post-type.php:67 482 | msgctxt "themes" 483 | msgid "Theme" 484 | msgstr "" 485 | 486 | #: includes/repo-theme/register-post-type.php:68 487 | msgctxt "themes" 488 | msgid "Add New" 489 | msgstr "" 490 | 491 | #: includes/repo-theme/register-post-type.php:69 492 | msgctxt "themes" 493 | msgid "Add New Theme Repo" 494 | msgstr "" 495 | 496 | #: includes/repo-theme/register-post-type.php:70 497 | msgctxt "themes" 498 | msgid "Edit Theme" 499 | msgstr "" 500 | 501 | #: includes/repo-theme/register-post-type.php:71 502 | msgctxt "themes" 503 | msgid "New Theme" 504 | msgstr "" 505 | 506 | #: includes/repo-theme/register-post-type.php:72 507 | msgctxt "themes" 508 | msgid "All Themes" 509 | msgstr "" 510 | 511 | #: includes/repo-theme/register-post-type.php:73 512 | msgctxt "themes" 513 | msgid "View Theme" 514 | msgstr "" 515 | 516 | #: includes/repo-theme/register-post-type.php:74 517 | msgctxt "themes" 518 | msgid "Search Theme" 519 | msgstr "" 520 | 521 | #: includes/repo-theme/register-post-type.php:75 522 | msgctxt "themes" 523 | msgid "No Theme Found" 524 | msgstr "" 525 | 526 | #: includes/repo-theme/register-post-type.php:76 527 | msgctxt "themes" 528 | msgid "No Theme Found in Trash" 529 | msgstr "" 530 | 531 | #: includes/repo-theme/register-post-type.php:77 532 | msgctxt "themes" 533 | msgid "Themes" 534 | msgstr "" 535 | 536 | #: includes/repo-theme/register-post-type.php:136 537 | msgctxt "themes" 538 | msgid "Theme Name" 539 | msgstr "" 540 | 541 | #: includes/repo-theme/register-post-type.php:156 542 | #: includes/repo-theme/register-post-type.php:159 543 | msgctxt "themes" 544 | msgid "Theme updated." 545 | msgstr "" 546 | 547 | #: includes/repo-theme/register-post-type.php:157 548 | msgctxt "themes" 549 | msgid "Theme field updated." 550 | msgstr "" 551 | 552 | #: includes/repo-theme/register-post-type.php:158 553 | msgctxt "themes" 554 | msgid "Theme field deleted." 555 | msgstr "" 556 | 557 | #: includes/repo-theme/register-post-type.php:161 558 | #, php-format 559 | msgctxt "themes" 560 | msgid "Theme restored to revision from %s" 561 | msgstr "" 562 | 563 | #: includes/repo-theme/register-post-type.php:162 564 | msgctxt "themes" 565 | msgid "Theme published." 566 | msgstr "" 567 | 568 | #: includes/repo-theme/register-post-type.php:163 569 | msgctxt "themes" 570 | msgid "Theme saved." 571 | msgstr "" 572 | 573 | #: includes/repo-theme/register-post-type.php:164 574 | msgctxt "themes" 575 | msgid "Theme submitted." 576 | msgstr "" 577 | 578 | #: includes/repo-theme/register-post-type.php:165 579 | #, php-format 580 | msgctxt "themes" 581 | msgid "Theme scheduled for: %1$s." 582 | msgstr "" 583 | 584 | #: includes/repo-theme/register-post-type.php:167 585 | msgctxt "themes" 586 | msgid "M j, Y @ H:i" 587 | msgstr "" 588 | 589 | #: includes/repo-theme/register-post-type.php:168 590 | msgctxt "themes" 591 | msgid "Theme draft updated." 592 | msgstr "" 593 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | --------------------------------------------------------------------------------