├── .gitignore ├── Gruntfile.js ├── assets └── css │ ├── admin.css │ ├── admin.less │ ├── menu.css │ ├── menu.less │ ├── mixins.css │ └── mixins.less ├── includes ├── class-wp-plugin-add-licence.php ├── class-wp-plugin-licencing-activation-api.php ├── class-wp-plugin-licencing-activations.php ├── class-wp-plugin-licencing-download-handler.php ├── class-wp-plugin-licencing-licences.php ├── class-wp-plugin-licencing-menus.php ├── class-wp-plugin-licencing-orders.php ├── class-wp-plugin-licencing-post-types.php ├── class-wp-plugin-licencing-products.php ├── class-wp-plugin-licencing-renewals.php ├── class-wp-plugin-licencing-shortcodes.php ├── class-wp-plugin-licencing-update-api.php ├── markdown.php ├── views │ ├── html-licence-data.php │ └── html-variation-licence-data.php └── wp-plugin-licencing-functions.php ├── package.json ├── templates ├── email-keys.php ├── lost-licence-email.php ├── lost-licence-form.php ├── my-licences.php └── new-licence-email.php └── wp-plugin-licencing.php /.gitignore: -------------------------------------------------------------------------------- 1 | /nbproject/private/ 2 | /node_modules/ 3 | project.xml 4 | project.properties 5 | .DS_Store 6 | Thumbs.db 7 | .buildpath 8 | .project 9 | .settings* 10 | sftp-config.json 11 | /deploy/ 12 | /wc-apidocs/ 13 | 14 | # Ignore all log files except for .htaccess 15 | /logs/* 16 | !/logs/.htaccess -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* jshint node:true */ 2 | module.exports = function( grunt ){ 3 | 'use strict'; 4 | 5 | grunt.initConfig({ 6 | // setting folder templates 7 | dirs: { 8 | css: 'assets/css', 9 | js: 'assets/js' 10 | }, 11 | 12 | // Compile all .less files. 13 | less: { 14 | compile: { 15 | options: { 16 | // These paths are searched for @imports 17 | paths: ['<%= dirs.css %>/'] 18 | }, 19 | files: [{ 20 | expand: true, 21 | cwd: '<%= dirs.css %>/', 22 | src: [ 23 | '*.less' 24 | ], 25 | dest: '<%= dirs.css %>/', 26 | ext: '.css' 27 | }] 28 | } 29 | }, 30 | 31 | // Minify all .css files. 32 | cssmin: { 33 | minify: { 34 | expand: true, 35 | cwd: '<%= dirs.css %>/', 36 | src: ['*.css'], 37 | dest: '<%= dirs.css %>/', 38 | ext: '.css' 39 | } 40 | }, 41 | 42 | // Minify .js files. 43 | uglify: { 44 | options: { 45 | preserveComments: 'some' 46 | }, 47 | frontend: { 48 | files: [{ 49 | expand: true, 50 | cwd: '<%= dirs.js %>', 51 | src: [ 52 | '*.js', 53 | '!*.min.js' 54 | ], 55 | dest: '<%= dirs.js %>', 56 | ext: '.min.js' 57 | }] 58 | }, 59 | }, 60 | 61 | // Watch changes for assets 62 | watch: { 63 | less: { 64 | files: ['<%= dirs.css %>/*.less'], 65 | tasks: ['less', 'cssmin'], 66 | }, 67 | js: { 68 | files: [ 69 | '<%= dirs.js %>/*js', 70 | '!<%= dirs.js %>/*.min.js', 71 | ], 72 | tasks: ['uglify'] 73 | } 74 | }, 75 | 76 | }); 77 | 78 | // Load NPM tasks to be used here 79 | grunt.loadNpmTasks( 'grunt-shell' ); 80 | grunt.loadNpmTasks( 'grunt-contrib-less' ); 81 | grunt.loadNpmTasks( 'grunt-contrib-cssmin' ); 82 | grunt.loadNpmTasks( 'grunt-contrib-uglify' ); 83 | grunt.loadNpmTasks( 'grunt-contrib-watch' ); 84 | grunt.loadNpmTasks( 'grunt-contrib-copy' ); 85 | grunt.loadNpmTasks( 'grunt-contrib-clean' ); 86 | grunt.loadNpmTasks( 'grunt-wp-deploy' ); 87 | 88 | // Register tasks 89 | grunt.registerTask( 'default', [ 90 | 'less', 91 | 'cssmin', 92 | 'uglify' 93 | ]); 94 | 95 | }; 96 | -------------------------------------------------------------------------------- /assets/css/admin.css: -------------------------------------------------------------------------------- 1 | .wp_plugin_licencing_meta_data p{overflow:hidden;margin:0 0 1em;padding:0 0 0 20%;position:relative;line-height:2em}.wp_plugin_licencing_meta_data label{width:20%;position:absolute;left:0;vertical-align:middle}.wp_plugin_licencing_meta_data input{width:50%;margin:1px 0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;vertical-align:middle}.wp_plugin_licencing_meta_data input.checkbox{width:auto;margin:4px 2px}.wp_plugin_licencing_meta_data .description{color:#999;display:block}table.wp-list-table .column-activation_email{width:14%}table.wp-list-table .column-licence_key{width:15em}table.wp-list-table .column-licence_key code{display:block}table.wp-list-table .column-activation_active,table.wp-list-table .column-activation_limit,table.wp-list-table .column-activations{text-align:center} -------------------------------------------------------------------------------- /assets/css/admin.less: -------------------------------------------------------------------------------- 1 | .wp_plugin_licencing_meta_data { 2 | p { 3 | overflow: hidden; 4 | margin: 0 0 1em; 5 | padding: 0 0 0 20%; 6 | position: relative; 7 | line-height: 2em; 8 | } 9 | label { 10 | width: 20%; 11 | position: absolute; 12 | left: 0; 13 | vertical-align: middle; 14 | } 15 | input { 16 | width: 50%; 17 | margin: 1px 0; 18 | -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ 19 | -moz-box-sizing: border-box; /* Firefox, other Gecko */ 20 | box-sizing: border-box; /* Opera/IE 8+ */ 21 | vertical-align: middle; 22 | } 23 | input.checkbox { 24 | width: auto; 25 | margin: 4px 2px; 26 | } 27 | .description { 28 | color: #999; 29 | display: block; 30 | } 31 | } 32 | table.wp-list-table { 33 | .column-activation_email { 34 | width: 14%; 35 | } 36 | .column-licence_key { 37 | width: 15em; 38 | code { 39 | display: block; 40 | } 41 | } 42 | .column-activations, 43 | .column-activation_limit, 44 | .column-activation_active { 45 | text-align: center; 46 | } 47 | } -------------------------------------------------------------------------------- /assets/css/menu.css: -------------------------------------------------------------------------------- 1 | #adminmenu #menu-posts-api_product .wp-menu-image:before{content:"\f312";font-family:dashicons!important;font-style:normal;font-weight:400;speak:none;display:inline-block;text-decoration:inherit;width:1em;text-align:center;font-variant:normal;text-transform:none;line-height:1em}#adminmenu #toplevel_page_wp_plugin_licencing_licences .wp-menu-image:before{content:"\f173";font-family:dashicons!important;font-style:normal;font-weight:400;speak:none;display:inline-block;text-decoration:inherit;width:1em;text-align:center;font-variant:normal;text-transform:none;line-height:1em} -------------------------------------------------------------------------------- /assets/css/menu.less: -------------------------------------------------------------------------------- 1 | /* Menu */ 2 | #adminmenu { 3 | #menu-posts-api_product { 4 | .wp-menu-image:before { 5 | content: "\f312"; 6 | font-family: "dashicons" !important; 7 | font-style: normal; 8 | font-weight: normal; 9 | speak: none; 10 | 11 | display: inline-block; 12 | text-decoration: inherit; 13 | width: 1em; 14 | text-align: center; 15 | 16 | /* For safety - reset parent styles, that can break glyph codes*/ 17 | font-variant: normal; 18 | text-transform: none; 19 | 20 | /* fix buttons height, for twitter bootstrap */ 21 | line-height: 1em; 22 | } 23 | } 24 | #toplevel_page_wp_plugin_licencing_licences { 25 | .wp-menu-image:before { 26 | content: "\f173"; 27 | font-family: "dashicons" !important; 28 | font-style: normal; 29 | font-weight: normal; 30 | speak: none; 31 | 32 | display: inline-block; 33 | text-decoration: inherit; 34 | width: 1em; 35 | text-align: center; 36 | 37 | /* For safety - reset parent styles, that can break glyph codes*/ 38 | font-variant: normal; 39 | text-transform: none; 40 | 41 | /* fix buttons height, for twitter bootstrap */ 42 | line-height: 1em; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /assets/css/mixins.css: -------------------------------------------------------------------------------- 1 | .clearfix{zoom:1}.clearfix:after,.clearfix:before{content:"";display:table}.clearfix:after{clear:both} -------------------------------------------------------------------------------- /assets/css/mixins.less: -------------------------------------------------------------------------------- 1 | @full-time: #90da36; 2 | @part-time: #f08d3c; 3 | @internship:#6033cc; 4 | @freelance: #3399cc; 5 | @temporary: #d93674; 6 | 7 | .clearfix { 8 | zoom: 1; /* For IE 6/7 (trigger hasLayout) */ 9 | 10 | &:before, &:after { 11 | content: ""; 12 | display: table; 13 | } 14 | &:after { 15 | clear: both; 16 | } 17 | } 18 | .border_radius(@radius:4px) { 19 | -webkit-border-radius:@radius; 20 | border-radius:@radius; 21 | } 22 | .border_radius_right(@radius:4px) { 23 | -webkit-border-top-right-radius: @radius; 24 | -webkit-border-bottom-right-radius: @radius; 25 | border-top-right-radius: @radius; 26 | border-bottom-right-radius: @radius; 27 | } 28 | .border_radius_left(@radius:4px) { 29 | -webkit-border-top-left-radius: @radius; 30 | -webkit-border-bottom-left-radius: @radius; 31 | border-top-left-radius: @radius; 32 | border-bottom-left-radius: @radius; 33 | } 34 | .border_radius_bottom(@radius:4px) { 35 | -webkit-border-bottom-left-radius: @radius; 36 | -webkit-border-bottom-right-radius: @radius; 37 | border-bottom-left-radius: @radius; 38 | border-bottom-right-radius: @radius; 39 | } 40 | .border_radius_top(@radius:4px) { 41 | -webkit-border-top-left-radius: @radius; 42 | -webkit-border-top-right-radius: @radius; 43 | border-top-left-radius: @radius; 44 | border-top-right-radius: @radius; 45 | } 46 | .opacity(@opacity:0.75) { 47 | filter:~"alpha(opacity=@opacity * 100)"; 48 | -khtml-opacity: @opacity; 49 | opacity: @opacity; 50 | } 51 | .box_shadow(@shadow_x:3px, @shadow_y:3px, @shadow_rad:3px, @shadow_in:3px, @shadow_color:#888) { 52 | box-shadow:@shadow_x @shadow_y @shadow_rad @shadow_in @shadow_color; 53 | -webkit-box-shadow:@shadow_x @shadow_y @shadow_rad @shadow_in @shadow_color; 54 | -moz-box-shadow:@shadow_x @shadow_y @shadow_rad @shadow_in @shadow_color; 55 | } 56 | .inset_box_shadow(@shadow_x:3px, @shadow_y:3px, @shadow_rad:3px, @shadow_in:3px, @shadow_color:#888) { 57 | box-shadow:inset @shadow_x @shadow_y @shadow_rad @shadow_in @shadow_color; 58 | -webkit-box-shadow:inset @shadow_x @shadow_y @shadow_rad @shadow_in @shadow_color; 59 | -moz-box-shadow:inset @shadow_x @shadow_y @shadow_rad @shadow_in @shadow_color; 60 | } 61 | .text_shadow(@shadow_x:3px, @shadow_y:3px, @shadow_rad:3px, @shadow_color:#fff) { 62 | text-shadow:@shadow_x @shadow_y @shadow_rad @shadow_color; 63 | } 64 | .vertical_gradient(@from: #000, @to: #FFF) { 65 | background: @from; 66 | background: -webkit-gradient(linear, left top, left bottom, from(@from), to(@to)); 67 | background: -webkit-linear-gradient(@from, @to); 68 | background: -moz-linear-gradient(center top, @from 0%, @to 100%); 69 | background: -moz-gradient(center top, @from 0%, @to 100%); 70 | } 71 | .transition(@selector:all, @animation:ease-in-out, @duration:.2s) { 72 | -webkit-transition:@selector @animation @duration; 73 | -moz-transition:@selector @animation @duration; 74 | -o-transition:@selector @animation @duration; 75 | transition:@selector @animation @duration; 76 | } 77 | .scale(@ratio:1.5){ 78 | -webkit-transform:scale(@ratio); 79 | -moz-transform:scale(@ratio); 80 | -ms-transform:scale(@ratio); 81 | -o-transform:scale(@ratio); 82 | transform:scale(@ratio); 83 | } 84 | .borderbox () { 85 | -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ 86 | -moz-box-sizing: border-box; /* Firefox, other Gecko */ 87 | box-sizing: border-box; /* Opera/IE 8+ */ 88 | } 89 | .darkorlighttextshadow ( @a, @opacity: 0.8 ) when (lightness(@a) >= 65%) { .text_shadow( 0, -1px, 0, rgba(0,0,0,@opacity) ); } 90 | .darkorlighttextshadow ( @a, @opacity: 0.8 ) when (lightness(@a) < 65%) { .text_shadow( 0, 1px, 0, rgba(255,255,255,@opacity) ); } -------------------------------------------------------------------------------- /includes/class-wp-plugin-add-licence.php: -------------------------------------------------------------------------------- 1 | save(); 17 | } 18 | } 19 | 20 | /** 21 | * Output the form 22 | */ 23 | public function form() { 24 | ?> 25 |
26 |29 | 30 | | 31 |32 | 33 | | 34 |
---|---|
37 | 38 | | 39 |40 | 41 | | 42 |
45 | 46 | | 47 |48 | 94 | | 95 |
98 | 99 | | 100 |101 | 104 | | 105 |
109 | 110 |
111 | id, '_is_api_product_licence', true ) ) { 162 | throw new Exception( __( 'Invalid product', 'wp-plugin-licencing' ) ); 163 | } 164 | 165 | if ( ! $activation_email && $user_id ) { 166 | $user = get_user_by( 'id', $user_id ); 167 | $activation_email = $user->user_email; 168 | } 169 | 170 | if ( empty( $activation_email ) || ! is_email( $activation_email ) ) { 171 | throw new Exception( __( 'A valid activation email is required', 'wp-plugin-licencing' ) ); 172 | } 173 | 174 | if ( ! $product->variation_id || ( ! $activation_limit = get_post_meta( $product->variation_id, '_licence_activation_limit', true ) ) ) { 175 | $activation_limit = get_post_meta( $product->id, '_licence_activation_limit', true ); 176 | } 177 | if ( ! $product->variation_id || ( ! $licence_expiry_days = get_post_meta( $product->variation_id, '_licence_expiry_days', true ) ) ) { 178 | $licence_expiry_days = get_post_meta( $product->id, '_licence_expiry_days', true ); 179 | } 180 | 181 | $data = array( 182 | 'order_id' => 0, 183 | 'licence_key' => $licence_key, 184 | 'activation_email' => $activation_email, 185 | 'user_id' => $user_id, 186 | 'product_id' => $product->variation_id ? $product->variation_id : $product->id, 187 | 'activation_limit' => $activation_limit, 188 | 'date_expires' => ! empty( $licence_expiry_days ) ? date( "Y-m-d H:i:s", strtotime( "+{$licence_expiry_days} days", current_time( 'timestamp' ) ) ) : '' 189 | ); 190 | 191 | if ( WP_Plugin_Licencing_Orders::save_licence_key( $data ) ) { 192 | ob_start(); 193 | 194 | // Try to get a user name 195 | if ( ! empty( $user ) && ! empty( $user->first_name ) ) { 196 | $user_first_name = $user->first_name; 197 | } else { 198 | $user_first_name = false; 199 | } 200 | 201 | wc_get_template( 'new-licence-email.php', array( 'key' => wppl_get_licence_from_key( $licence_key ), 'user_first_name' => $user_first_name ), 'wp-plugin-licencing', WP_PLUGIN_LICENCING_PLUGIN_DIR . '/templates/' ); 202 | 203 | // Get contents 204 | $message = ob_get_clean(); 205 | 206 | wp_mail( $activation_email, __( 'Your licence keys for "WP Job Manager"', 'wp-plugin-licencing' ), $message ); 207 | 208 | $admin_message = sprintf( __( 'Licence has been emailed to %s.', 'wp-plugin-licencing' ), $activation_email ); 209 | echo sprintf( '%s
%s
' . $item->licence_key . '
' . '';
44 | case 'api_product_id' :
45 | return esc_html( $item->api_product_id );
46 | case 'instance' :
47 | return $item->instance ? esc_html( $item->instance ) : __( 'n/a', 'wp-plugin-licencing' );
48 | case 'activation_date' :
49 | return ( $item->activation_date ) ? date_i18n( get_option( 'date_format' ), strtotime( $item->activation_date ) ) : __( 'n/a', 'wp-plugin-licencing' );
50 | case 'activation_active' :
51 | return $item->activation_active ? '✔' : '-';
52 | }
53 | }
54 |
55 | /**
56 | * column_cb function.
57 | *
58 | * @access public
59 | * @param mixed $item
60 | */
61 | public function column_cb( $item ){
62 | return sprintf(
63 | '',
64 | 'activation_id',
65 | $item->activation_id
66 | );
67 | }
68 |
69 | /**
70 | * get_columns function.
71 | *
72 | * @access public
73 | */
74 | public function get_columns(){
75 | $columns = array(
76 | 'cb' => '',
77 | 'licence_key' => __( 'Licence key', 'wp-plugin-licencing' ),
78 | 'api_product_id' => __( 'API Product ID', 'wp-plugin-licencing' ),
79 | 'activation_date' => __( 'Activation date', 'wp-plugin-licencing' ),
80 | 'instance' => __( 'Instance', 'wp-plugin-licencing' ),
81 | 'activation_active' => __( 'Active?', 'wp-plugin-licencing' ),
82 | );
83 | return $columns;
84 | }
85 |
86 | /**
87 | * get_sortable_columns function.
88 | *
89 | * @access public
90 | */
91 | public function get_sortable_columns() {
92 | $sortable_columns = array(
93 | 'activation_date' => array( 'activation_date', true ), //true means its already sorted
94 | 'date_expires' => array( 'date_expires', false ),
95 | 'order_id' => array( 'order_id', false ),
96 | 'api_product_id' => array( 'api_product_id', false ),
97 | 'activation_email' => array( 'activation_email', false ),
98 | );
99 | return $sortable_columns;
100 | }
101 |
102 | /**
103 | * get_bulk_actions
104 | * @return array
105 | */
106 | public function get_bulk_actions() {
107 | $actions = array(
108 | 'activate' => __( 'Activate', 'wp-plugin-licencing' ),
109 | 'deactivate' => __( 'Deactivate', 'wp-plugin-licencing' ),
110 | 'delete' => __( 'Delete', 'wp-plugin-licencing' )
111 | );
112 | return $actions;
113 | }
114 |
115 | /**
116 | * Process bulk actions
117 | */
118 | public function process_bulk_action() {
119 | global $wpdb;
120 |
121 | if ( ! isset( $_POST['activation_id'] ) ) {
122 | return;
123 | }
124 |
125 | $items = array_map( 'absint', $_POST['activation_id'] );
126 |
127 | if ( $items ) {
128 | switch ( $this->current_action() ) {
129 | case 'activate' :
130 | foreach ( $items as $id ) {
131 | $wpdb->update( "{$wpdb->prefix}wp_plugin_licencing_activations", array( 'activation_active' => 1 ), array( 'activation_id' => $id ) );
132 | }
133 | echo '' . sprintf( __( '%d activations activated', 'wp-plugin-licencing' ), sizeof( $items ) ) . '
' . sprintf( __( '%d activations deactivated', 'wp-plugin-licencing' ), sizeof( $items ) ) . '
' . sprintf( __( '%d activations deleted', 'wp-plugin-licencing' ), sizeof( $items ) ) . '
' . $item->licence_key . '
';
44 | case 'activation_email' :
45 | return $item->activation_email;
46 | case 'product_id' :
47 | $product = wppl_get_licence_product( $item->product_id );
48 |
49 | return ( $product ) ? '' . esc_html( $product->post_title ) . '' : __( 'n/a', 'wp-plugin-licencing' );
50 | case 'user_id' :
51 | $user = get_user_by( 'ID', $item->user_id );
52 |
53 | return ( $item->user_id ) ? '#' . esc_html( $item->user_id ) . '→' : __( 'n/a', 'wp-plugin-licencing' );
54 | case 'activations' :
55 | $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT( activation_id ) FROM {$wpdb->prefix}wp_plugin_licencing_activations WHERE activation_active = 1 AND licence_key=%s;", $item->licence_key ) );
56 |
57 | return '' . absint( $count ) . ' →';
58 | case 'activation_limit' :
59 | return $item->activation_limit ? sprintf( __( '%d per product', 'wp-plugin-licencing' ), absint( $item->activation_limit ) ) : __( 'n/a', 'wp-plugin-licencing' );
60 | case 'order_id' :
61 | return $item->order_id > 0 ? '#' . absint( $item->order_id ) . ' →' : __( 'n/a', 'wp-plugin-licencing' );
62 | case 'date_created' :
63 | return ( $item->date_created ) ? date_i18n( get_option( 'date_format' ), strtotime( $item->date_created ) ) : __( 'n/a', 'wp-plugin-licencing' );
64 | case 'date_expires' :
65 | return ( $item->date_expires ) ? date_i18n( get_option( 'date_format' ), strtotime( $item->date_expires ) ) : __( 'n/a', 'wp-plugin-licencing' );
66 | }
67 | }
68 |
69 | /**
70 | * column_cb function.
71 | *
72 | * @access public
73 | * @param mixed $item
74 | */
75 | public function column_cb( $item ){
76 | return sprintf(
77 | '',
78 | 'licence_key',
79 | $item->licence_key
80 | );
81 | }
82 |
83 | /**
84 | * get_columns function.
85 | *
86 | * @access public
87 | */
88 | public function get_columns(){
89 | $columns = array(
90 | 'cb' => '',
91 | 'licence_key' => __( 'Licence key', 'wp-plugin-licencing' ),
92 | 'activation_email' => __( 'Activation email', 'wp-plugin-licencing' ),
93 | 'product_id' => __( 'Product', 'wp-plugin-licencing' ),
94 | 'order_id' => __( 'Order ID', 'wp-plugin-licencing' ),
95 | 'user_id' => __( 'User ID', 'wp-plugin-licencing' ),
96 | 'activation_limit' => __( 'Activation limit', 'wp-plugin-licencing' ),
97 | 'activations' => __( 'Activations', 'wp-plugin-licencing' ),
98 | 'date_created' => __( 'Date created', 'wp-plugin-licencing' ),
99 | 'date_expires' => __( 'Date expires', 'wp-plugin-licencing' )
100 | );
101 | return $columns;
102 | }
103 |
104 | /**
105 | * get_sortable_columns function.
106 | *
107 | * @access public
108 | */
109 | public function get_sortable_columns() {
110 | $sortable_columns = array(
111 | 'date_created' => array( 'date_created', true ), //true means its already sorted
112 | 'date_expires' => array( 'date_expires', false ),
113 | 'order_id' => array( 'order_id', false ),
114 | 'user_id' => array( 'user_id', false ),
115 | 'product_id' => array( 'product_id', false ),
116 | 'activation_email' => array( 'activation_email', false ),
117 | );
118 | return $sortable_columns;
119 | }
120 |
121 | /**
122 | * get_bulk_actions
123 | * @return array
124 | */
125 | public function get_bulk_actions() {
126 | $actions = array(
127 | 'deactivate' => __( 'Deactivate', 'wp-plugin-licencing' ),
128 | 'delete' => __( 'Delete', 'wp-plugin-licencing' )
129 | );
130 | return $actions;
131 | }
132 |
133 | /**
134 | * Process bulk actions
135 | */
136 | public function process_bulk_action() {
137 | global $wpdb;
138 |
139 | if ( ! isset( $_POST['licence_key'] ) ) {
140 | return;
141 | }
142 |
143 | $items = array_map( 'sanitize_text_field', $_POST['licence_key'] );
144 |
145 | if ( $items ) {
146 | switch ( $this->current_action() ) {
147 | case 'deactivate' :
148 | foreach ( $items as $id ) {
149 | $wpdb->update( "{$wpdb->prefix}wp_plugin_licencing_activations", array( 'activation_active' => 0 ), array( 'licence_key' => $id ) );
150 | }
151 | echo '' . sprintf( __( '%d keys deactivated', 'wp-plugin-licencing' ), sizeof( $items ) ) . '
' . sprintf( __( '%d keys deleted', 'wp-plugin-licencing' ), sizeof( $items ) ) . '
' . $post->post_name . '
';
86 | break;
87 | case "version" :
88 | case "last_updated" :
89 | $data = get_post_meta( $post->ID, '_' . $column, true );
90 | echo esc_html( $data );
91 | break;
92 | case "package" :
93 | $data = get_post_meta( $post->ID, '_package', true );
94 | echo '' . esc_html( basename( $data ) ) . '
';
95 | break;
96 | }
97 | }
98 |
99 | /**
100 | * Scripts
101 | */
102 | public function admin_enqueue_scripts() {
103 | wp_enqueue_style( 'wp_plugin_licencing_admin_css', WP_PLUGIN_LICENCING_PLUGIN_URL . '/assets/css/admin.css' );
104 | wp_enqueue_style( 'wp_plugin_licencing_menu_css', WP_PLUGIN_LICENCING_PLUGIN_URL . '/assets/css/menu.css' );
105 | wp_enqueue_media();
106 | }
107 |
108 | /**
109 | * register_post_types function.
110 | *
111 | * @access public
112 | * @return void
113 | */
114 | public function register_post_types() {
115 | if ( post_type_exists( "api_product" ) ) {
116 | return;
117 | }
118 |
119 | /**
120 | * Post types
121 | */
122 | $singular = __( 'API Product', 'wp-plugin-licencing' );
123 | $plural = __( 'API Products', 'wp-plugin-licencing' );
124 |
125 | register_post_type( "api_product",
126 | apply_filters( "register_post_type_api_product", array(
127 | 'labels' => array(
128 | 'name' => $plural,
129 | 'singular_name' => $singular,
130 | 'menu_name' => $plural,
131 | 'all_items' => sprintf( __( 'All %s', 'wp-plugin-licencing' ), $plural ),
132 | 'add_new' => __( 'Add New', 'wp-plugin-licencing' ),
133 | 'add_new_item' => sprintf( __( 'Add %s', 'wp-plugin-licencing' ), $singular ),
134 | 'edit' => __( 'Edit', 'wp-plugin-licencing' ),
135 | 'edit_item' => sprintf( __( 'Edit %s', 'wp-plugin-licencing' ), $singular ),
136 | 'new_item' => sprintf( __( 'New %s', 'wp-plugin-licencing' ), $singular ),
137 | 'view' => sprintf( __( 'View %s', 'wp-plugin-licencing' ), $singular ),
138 | 'view_item' => sprintf( __( 'View %s', 'wp-plugin-licencing' ), $singular ),
139 | 'search_items' => sprintf( __( 'Search %s', 'wp-plugin-licencing' ), $plural ),
140 | 'not_found' => sprintf( __( 'No %s found', 'wp-plugin-licencing' ), $plural ),
141 | 'not_found_in_trash' => sprintf( __( 'No %s found in trash', 'wp-plugin-licencing' ), $plural ),
142 | 'parent' => sprintf( __( 'Parent %s', 'wp-plugin-licencing' ), $singular )
143 | ),
144 | 'description' => __( 'This is where you can create and manage api products.', 'wp-plugin-licencing' ),
145 | 'public' => false,
146 | 'show_ui' => true,
147 | 'capability_type' => 'post',
148 | 'publicly_queryable' => false,
149 | 'exclude_from_search' => true,
150 | 'hierarchical' => false,
151 | 'rewrite' => false,
152 | 'query_var' => false,
153 | 'supports' => array( 'title' ),
154 | 'has_archive' => false,
155 | 'show_in_nav_menus' => false
156 | ) )
157 | );
158 | }
159 |
160 | /**
161 | * readme_fields
162 | *
163 | * @access public
164 | * @return array
165 | */
166 | public function readme_fields() {
167 | global $post;
168 |
169 | return apply_filters( 'wp_plugin_licencing_readme_fields', array(
170 | 'post_name' => array(
171 | 'label' => __( 'API Product ID', 'wp-plugin-licencing' ),
172 | 'placeholder' => __( 'your-plugin-name', 'wp-plugin-licencing' ),
173 | 'description' => __( 'A unique identifier for the API Product. Stored as the post_name.', 'wp-plugin-licencing' ),
174 | 'type' => 'text',
175 | 'value' => $post->post_name
176 | ),
177 | '_version' => array(
178 | 'label' => __( 'Version', 'wp-plugin-licencing' ),
179 | 'placeholder' => __( 'x.x.x', 'wp-plugin-licencing' ),
180 | 'description' => __( 'The current version number of the plugin.', 'wp-plugin-licencing' )
181 | ),
182 | '_last_updated' => array(
183 | 'label' => __( 'Date', 'wp-plugin-licencing' ),
184 | 'placeholder' => __( 'yyyy-mm-dd', 'wp-plugin-licencing' ),
185 | 'description' => __( 'The date of the last update.', 'wp-plugin-licencing' )
186 | ),
187 | '_package' => array(
188 | 'label' => __( 'Package', 'wp-plugin-licencing' ),
189 | 'type' => 'file',
190 | 'description' => __( 'The plugin package zip file.', 'wp-plugin-licencing' )
191 | ),
192 | '_plugin_uri' => array(
193 | 'label' => __( 'Plugin URI', 'wp-plugin-licencing' )
194 | ),
195 | '_author' => array(
196 | 'label' => __( 'Author', 'wp-plugin-licencing' ),
197 | 'placeholder' => ''
198 | ),
199 | '_author_uri' => array(
200 | 'label' => __( 'Author URI', 'wp-plugin-licencing' ),
201 | 'placeholder' => ''
202 | ),
203 | '_requires_wp_version' => array(
204 | 'label' => __( 'Requries at least', 'wp-plugin-licencing' ),
205 | 'placeholder' => __( 'e.g. 3.8', 'wp-plugin-licencing' )
206 | ),
207 | '_tested_wp_version' => array(
208 | 'label' => __( 'Tested up to', 'wp-plugin-licencing' ),
209 | 'placeholder' => __( 'e.g. 3.9', 'wp-plugin-licencing' )
210 | ),
211 | 'content' => array(
212 | 'label' => __( 'Description', 'wp-plugin-licencing' ),
213 | 'placeholder' => __( 'Content describing the plugin', 'wp-plugin-licencing' ),
214 | 'type' => 'textarea',
215 | 'value' => $post->post_content
216 | ),
217 | '_changelog' => array(
218 | 'label' => __( 'Changelog', 'wp-plugin-licencing' ),
219 | 'type' => 'textarea'
220 | )
221 | ) );
222 | }
223 |
224 | /**
225 | * add_meta_boxes function.
226 | *
227 | * @access public
228 | * @return void
229 | */
230 | public function add_meta_boxes() {
231 | add_meta_box( 'api_product_data', __( 'API Product Data', 'wp-plugin-licencing' ), array( $this, 'api_product_data' ), 'api_product', 'normal', 'high' );
232 | }
233 |
234 | /**
235 | * input_text function.
236 | *
237 | * @param mixed $key
238 | * @param mixed $field
239 | */
240 | public function input_file( $key, $field ) {
241 | global $thepostid;
242 |
243 | if ( empty( $field['value'] ) ) {
244 | $field['value'] = get_post_meta( $thepostid, $key, true );
245 | }
246 |
247 | if ( ! isset( $field['placeholder'] ) ) {
248 | $field['placeholder'] = '';
249 | }
250 |
251 | ?>
252 | 253 | 254 | 255 | 256 | 257 |
258 | 296 | 317 |318 | 319 | 320 | 321 | 322 |
323 | 343 |344 | 345 | 346 | 347 | 348 |
349 | 365 |366 | 367 | 374 | 375 | 376 |
377 | ID; 393 | 394 | echo ' '; 413 | } 414 | 415 | /** 416 | * save_post function. 417 | * 418 | * @access public 419 | * 420 | * @param mixed $post_id 421 | * @param mixed $post 422 | * 423 | * @return void 424 | */ 425 | public function save_post( $post_id, $post ) { 426 | if ( empty( $post_id ) || empty( $post ) || empty( $_POST ) ) { 427 | return; 428 | } 429 | if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { 430 | return; 431 | } 432 | if ( is_int( wp_is_post_revision( $post ) ) ) { 433 | return; 434 | } 435 | if ( is_int( wp_is_post_autosave( $post ) ) ) { 436 | return; 437 | } 438 | if ( empty( $_POST['wp_plugin_licencing_nonce'] ) || ! wp_verify_nonce( $_POST['wp_plugin_licencing_nonce'], 'save_meta_data' ) ) { 439 | return; 440 | } 441 | if ( ! current_user_can( 'edit_post', $post_id ) ) { 442 | return; 443 | } 444 | if ( $post->post_type != 'api_product' ) { 445 | return; 446 | } 447 | 448 | do_action( 'wp_plugin_licencing_save_api_product', $post_id, $post ); 449 | } 450 | 451 | /** 452 | * save_api_product_data function. 453 | * 454 | * @access public 455 | * 456 | * @param mixed $post_id 457 | * @param mixed $post 458 | * 459 | * @return void 460 | */ 461 | public function save_api_product_data( $post_id, $post ) { 462 | global $wpdb; 463 | 464 | // Save fields 465 | foreach ( $this->readme_fields() as $key => $field ) { 466 | // Expirey date 467 | if ( '_last_updated' === $key ) { 468 | if ( ! empty( $_POST[$key] ) ) { 469 | update_post_meta( $post_id, $key, date( 'Y-m-d', strtotime( sanitize_text_field( $_POST[$key] ) ) ) ); 470 | } else { 471 | update_post_meta( $post_id, $key, date( 'Y-m-d' ) ); 472 | } 473 | } elseif ( 'content' === $key ) { 474 | continue; 475 | } elseif ( 'post_name' === $key ) { 476 | continue; 477 | } // Everything else 478 | else { 479 | $type = ! empty( $field['type'] ) ? $field['type'] : ''; 480 | 481 | switch ( $type ) { 482 | case 'textarea' : 483 | update_post_meta( $post_id, $key, wp_kses_post( stripslashes( $_POST[$key] ) ) ); 484 | break; 485 | default : 486 | if ( is_array( $_POST[$key] ) ) { 487 | update_post_meta( $post_id, $key, array_map( 'sanitize_text_field', $_POST[$key] ) ); 488 | } else { 489 | update_post_meta( $post_id, $key, sanitize_text_field( $_POST[$key] ) ); 490 | } 491 | break; 492 | } 493 | } 494 | } 495 | 496 | // Get the plugin version 497 | $plugin_version = get_post_meta( $post_id, '_version', true ); 498 | 499 | delete_transient( 'plugininfo_' . md5( $post->post_name . $plugin_version ) ); 500 | } 501 | } 502 | 503 | new WP_Plugin_Licencing_Post_Types(); -------------------------------------------------------------------------------- /includes/class-wp-plugin-licencing-products.php: -------------------------------------------------------------------------------- 1 | '_is_api_product_licence', 30 | 'wrapper_class' => 'show_if_simple show_if_variable', 31 | 'label' => __( 'API Product Licence', 'wp-plugin-licencing' ), 32 | 'description' => __( 'Enable this option if this is a licence for an API Product', 'wp-plugin-licencing' ) 33 | ); 34 | return $options; 35 | } 36 | 37 | /** 38 | * adds the panel to the product interface 39 | */ 40 | public function licence_data() { 41 | global $post; 42 | $post_id = $post->ID; 43 | $current_api_products = (array) json_decode( get_post_meta( $post->ID, '_api_product_permissions', true ) ); 44 | $api_products = get_posts( array( 45 | 'numberposts' => -1, 46 | 'orderby' => 'title', 47 | 'post_type' => 'api_product', 48 | 'post_status' => array( 'publish' ), 49 | ) ); 50 | include( 'views/html-licence-data.php' ); 51 | } 52 | 53 | /** 54 | * add the panel for variations 55 | */ 56 | public function variable_licence_data( $loop, $variation_data, $variation ) { 57 | global $post, $thepostid; 58 | include( 'views/html-variation-licence-data.php' ); 59 | } 60 | 61 | /** 62 | * Save data 63 | */ 64 | public function save_licence_data() { 65 | global $post; 66 | 67 | if ( ! empty( $_POST['_is_api_product_licence'] ) ) { 68 | update_post_meta( $post->ID, '_is_api_product_licence', 'yes' ); 69 | } else { 70 | update_post_meta( $post->ID, '_is_api_product_licence', 'no' ); 71 | } 72 | 73 | update_post_meta( $post->ID, '_api_product_permissions', json_encode( array_map( 'absint', (array) ( isset( $_POST['api_product_permissions'] ) ? $_POST['api_product_permissions'] : array() ) ) ) ); 74 | update_post_meta( $post->ID, '_licence_activation_limit', sanitize_text_field( $_POST['_licence_activation_limit'] ) ); 75 | update_post_meta( $post->ID, '_licence_expiry_days', sanitize_text_field( $_POST['_licence_expiry_days'] ) ); 76 | } 77 | 78 | /** 79 | * Save variation data 80 | */ 81 | public function save_variable_licence_data( $variation_id, $i ) { 82 | $variation_licence_activation_limit = $_POST['_variation_licence_activation_limit']; 83 | $variation_licence_expiry_days = $_POST['_variation_licence_expiry_days']; 84 | 85 | update_post_meta( $variation_id, '_licence_activation_limit', sanitize_text_field( $variation_licence_activation_limit[ $i ] ) ); 86 | update_post_meta( $variation_id, '_licence_expiry_days', sanitize_text_field( $variation_licence_expiry_days[ $i ] ) ); 87 | } 88 | 89 | } 90 | 91 | new WP_Plugin_Licencing_Products(); -------------------------------------------------------------------------------- /includes/class-wp-plugin-licencing-renewals.php: -------------------------------------------------------------------------------- 1 | get_row( $wpdb->prepare( " 30 | SELECT * FROM {$wpdb->prefix}wp_plugin_licencing_licences 31 | WHERE licence_key = %s 32 | AND ( user_id = %d OR user_id = 0 ) 33 | ", $licence_key, get_current_user_id() ) ); 34 | 35 | // Renewable? 36 | if ( ! $licence ) { 37 | wc_add_notice( __( 'Invalid licence', 'wp-plugin-licencing' ), 'error' ); 38 | return; 39 | } 40 | 41 | if ( ! $licence->date_expires || strtotime( $licence->date_expires ) > current_time( 'timestamp' ) ) { 42 | wc_add_notice( __( 'This licence does not need to be renewed yet', 'wp-plugin-licencing' ), 'notice' ); 43 | return; 44 | } 45 | 46 | // Purchasable? 47 | $product = get_product( $licence->product_id ); 48 | 49 | if ( ! $product->is_purchasable() ) { 50 | wc_add_notice( __( 'This product can no longer be purchased', 'wp-plugin-licencing' ), 'error' ); 51 | return; 52 | } 53 | 54 | // Add to cart 55 | WC()->cart->empty_cart(); 56 | WC()->cart->add_to_cart( $licence->product_id, 1, '', '', array( 57 | 'renewing_key' => $licence_key 58 | ) ); 59 | 60 | // Message 61 | wc_add_notice( sprintf( __( 'The product has been added to your cart with a %d%% discount.', 'wp-plugin-licencing' ), apply_filters( 'wp_plugin_licencing_renewal_discount_percent', 30 ) ), 'success' ); 62 | 63 | // Redirect to checkout 64 | wp_redirect( get_permalink( wc_get_page_id( 'checkout' ) ) ); 65 | exit; 66 | } 67 | } 68 | 69 | /** 70 | * Change price in cart to discount the upgrade 71 | */ 72 | public function add_cart_item( $cart_item ) { 73 | if ( isset( $cart_item['renewing_key'] ) ) { 74 | $price = $cart_item['data']->get_price(); 75 | $discount = ( $price / 100 ) * apply_filters( 'wp_plugin_licencing_renewal_discount_percent', 30 ); 76 | $discounted_price = $price - $discount; 77 | 78 | $cart_item['data']->set_price( $discounted_price ); 79 | $cart_item['data']->get_post_data(); 80 | $cart_item['data']->post->post_title .= ' (' . __( 'Renewal', 'wp-plugin-licencing' ) . ')'; 81 | } 82 | return $cart_item; 83 | } 84 | 85 | /** 86 | * get_cart_item_from_session function. 87 | */ 88 | public function get_cart_item_from_session( $cart_item, $values ) { 89 | if ( isset( $values['renewing_key'] ) ) { 90 | $price = $cart_item['data']->get_price(); 91 | $discount = ( $price / 100 ) * apply_filters( 'wp_plugin_licencing_renewal_discount_percent', 30 ); 92 | $discounted_price = $price - $discount; 93 | 94 | $cart_item['data']->set_price( $discounted_price ); 95 | $cart_item['data']->get_post_data(); 96 | $cart_item['data']->post->post_title .= ' (' . __( 'Renewal', 'wp-plugin-licencing' ) . ')'; 97 | 98 | $cart_item['renewing_key'] = $values['renewing_key']; 99 | } 100 | return $cart_item; 101 | } 102 | 103 | /** 104 | * order_item_meta function for storing the meta in the order line items 105 | */ 106 | public function order_item_meta( $item_id, $values ) { 107 | if ( isset( $values['renewing_key'] ) ) { 108 | wc_add_order_item_meta( $item_id, __('_renewing_key', 'wp-plugin-licencing' ), $values['renewing_key'] ); 109 | } 110 | } 111 | } 112 | 113 | new WP_Plugin_Licencing_Renewals(); -------------------------------------------------------------------------------- /includes/class-wp-plugin-licencing-shortcodes.php: -------------------------------------------------------------------------------- 1 | post_content, '[lost_licence_key_form' ) ) { 29 | $this->lost_licence_key_form_handler(); 30 | } elseif ( ! empty( $_GET['deactivate_licence'] ) ) { 31 | $this->deactivate_licence(); 32 | } 33 | } 34 | 35 | /** 36 | * Deactivate a licence from the my account page 37 | */ 38 | public function deactivate_licence() { 39 | global $wpdb; 40 | 41 | if ( is_user_logged_in() && ! empty( $_GET['deactivate_licence'] ) ) { 42 | $activation_id = sanitize_text_field( $_GET['deactivate_licence'] ); 43 | $licence_key = sanitize_text_field( $_GET['licence_key'] ); 44 | $activation_email = sanitize_text_field( $_GET['activation_email'] ); 45 | $licence = wppl_get_licence_from_key( $licence_key ); 46 | 47 | // Validation 48 | if ( ! $licence ) { 49 | wp_die( __( 'Invalid or expired licence key.', 'wp-plugin-licencing' ) ); 50 | } 51 | if ( $licence->user_id && $licence->user_id != get_current_user_id() ) { 52 | wp_die( __( 'This licence does not appear to be yours.', 'wp-plugin-licencing' ) ); 53 | } 54 | if ( ! is_email( $activation_email ) || $activation_email != $licence->activation_email ) { 55 | wp_die( __( 'Invalid activation email address.', 'wp-plugin-licencing' ) ); 56 | } 57 | 58 | if ( $wpdb->update( "{$wpdb->prefix}wp_plugin_licencing_activations", array( 'activation_active' => 0 ), array( 59 | 'activation_id' => $activation_id, 60 | 'licence_key' => $licence_key 61 | ) ) ) { 62 | wc_add_notice( __( 'Licence successfully deactivated.' ), 'success' ); 63 | } else { 64 | wc_add_notice( __( 'The licence could not be deactivated.' ), 'error' ); 65 | } 66 | } 67 | } 68 | 69 | /** 70 | * Handles actions on candidate dashboard 71 | */ 72 | public function lost_licence_key_form_handler() { 73 | if ( ! empty( $_REQUEST['submit_lost_licence_form'] ) ) { 74 | $activation_email = sanitize_text_field( $_REQUEST['activation_email'] ); 75 | 76 | if ( ! is_email( $activation_email ) ) { 77 | wc_add_notice( __( 'Invalid email address.' ), 'error' ); 78 | return; 79 | } 80 | 81 | $keys = wppl_get_licences_from_activation_email( $activation_email ); 82 | 83 | if ( ! $keys ) { 84 | wc_add_notice( __( 'No licences found.' ), 'error' ); 85 | } else { 86 | ob_start(); 87 | 88 | // Try to get a user name 89 | $user = get_user_by( 'email', $activation_email ); 90 | if ( $user && ! empty( $user->first_name ) ) { 91 | $user_first_name = $user->first_name; 92 | } else { 93 | $user_first_name = false; 94 | } 95 | 96 | wc_get_template( 'lost-licence-email.php', array( 'keys' => $keys, 'activation_email' => $activation_email, 'blogname' => get_option( 'blogname' ), 'user_first_name' => $user_first_name ), 'wp-plugin-licencing', WP_PLUGIN_LICENCING_PLUGIN_DIR . '/templates/' ); 97 | 98 | // Get contents 99 | $message = ob_get_clean(); 100 | 101 | if ( wp_mail( $activation_email, __( 'Your licence keys for WP Job Manager', 'wp-plugin-licencing' ), $message ) ) { 102 | wc_add_notice( sprintf( __( 'Your licences have been emailed to %s.' ), $activation_email ), 'success' ); 103 | } else { 104 | wc_add_notice( __( 'Your licences could not be sent. Please contact us for support.' ), 'error' ); 105 | } 106 | } 107 | } 108 | } 109 | 110 | /** 111 | * Shows the lost licence key form 112 | */ 113 | public function lost_licence_key_form( $atts ) { 114 | ob_start(); 115 | 116 | wc_get_template( 'lost-licence-form.php', array(), 'wp-plugin-licencing', WP_PLUGIN_LICENCING_PLUGIN_DIR . '/templates/' ); 117 | 118 | return ob_get_clean(); 119 | } 120 | } 121 | 122 | new WP_Plugin_Licencing_Shortcodes(); -------------------------------------------------------------------------------- /includes/class-wp-plugin-licencing-update-api.php: -------------------------------------------------------------------------------- 1 | hide_errors(); 18 | nocache_headers(); 19 | 20 | $user_agent = $_SERVER['HTTP_USER_AGENT']; 21 | 22 | if ( stristr( $user_agent, 'WordPress' ) === false ) { 23 | die(); 24 | } 25 | 26 | if ( isset( $request['request'] ) ) { 27 | $this->request = array_map( 'sanitize_text_field', $request ); 28 | } else { 29 | die(); 30 | } 31 | 32 | switch ( $this->request['request'] ) { 33 | case 'pluginupdatecheck' : 34 | $this->plugin_update_check(); 35 | break; 36 | case 'plugininformation' : 37 | $this->plugin_information(); 38 | break; 39 | default : 40 | die(); 41 | break; 42 | } 43 | } 44 | 45 | /** 46 | * Trigger error message 47 | * @param int $code 48 | * @param string $message 49 | */ 50 | private function trigger_error( $code, $message ) { 51 | $response = new stdClass(); 52 | 53 | switch ( $this->request['request'] ) { 54 | case 'pluginupdatecheck' : 55 | $response->slug = ''; 56 | $response->plugin = ''; 57 | $response->new_version = ''; 58 | $response->url = ''; 59 | $response->package = ''; 60 | break; 61 | case 'plugininformation' : 62 | $response->name = ''; 63 | $response->slug = ''; 64 | $response->plugin = ''; 65 | $response->version = ''; 66 | $response->last_updated = ''; 67 | $response->download_link = ''; 68 | $response->author = ''; 69 | $response->requires = ''; 70 | $response->tested = ''; 71 | $response->homepage = ''; 72 | $response->sections = ''; 73 | break; 74 | } 75 | 76 | $response->errors = array( $code => $message ); 77 | die( serialize( $response ) ); 78 | } 79 | 80 | /** 81 | * Send response 82 | */ 83 | private function send_response( $data ) { 84 | die( serialize( $data ) ); 85 | } 86 | 87 | /** 88 | * Check access to plugin update API 89 | */ 90 | public function check_access() { 91 | // Check data 92 | if ( empty( $this->request['licence_key'] ) ) { 93 | $this->trigger_error( 'no_key', 'no_key' ); 94 | } 95 | if ( empty( $this->request['email'] ) || empty( $this->request['api_product_id'] ) || empty( $this->request['instance'] ) || empty( $this->request['version'] ) || empty( $this->request['plugin_name'] ) ) { 96 | $this->trigger_error( 'invalid_request', 'invalid_request' ); 97 | } 98 | 99 | // Check licence 100 | $licence = wppl_get_licence_from_key( $this->request['licence_key'] ); 101 | $api_product_post_id = wppl_get_api_product_post_id( $this->request['api_product_id'] ); 102 | 103 | if ( ! $api_product_post_id ) { 104 | $this->trigger_error( 'invalid_request', 'invalid_request' ); 105 | } 106 | if ( ! $licence || ! is_email( $this->request['email'] ) || strtolower( $this->request['email'] ) != strtolower( $licence->activation_email ) || ! in_array( $api_product_post_id, wppl_get_licence_api_product_permissions( $licence->product_id ) ) ) { 107 | 108 | $this->trigger_error( 'invalid_key', sprintf( __( 'The licence for%s
is invalid or has expired. To continue to receive support and updates you must obtain an updated licence key. If you have an account, expired keys can be renewed via your account dashboard.', 'wp-plugin-licencing' ), $this->request['api_product_id'], get_permalink( wc_get_page_id( 'myaccount' ) ) ) );
109 | }
110 | if ( ! wppl_is_licence_activated( $this->request['licence_key'], $this->request['api_product_id'], $this->request['instance'] ) ) {
111 |
112 | $this->trigger_error( 'no_activation', sprintf( __( 'The licence is no longer activated on this site. Reactivate the licence to receive updates for %s
.', 'wp-plugin-licencing' ), $this->request['api_product_id'] ) );
113 | }
114 | }
115 |
116 | /**
117 | * Plugin update check
118 | */
119 | public function plugin_update_check() {
120 | $this->check_access();
121 |
122 | $licence = wppl_get_licence_from_key( $this->request['licence_key'] );
123 | $api_product_post_id = wppl_get_api_product_post_id( $this->request['api_product_id'] );
124 | $data = new stdClass();
125 | $data->plugin = $this->request['plugin_name'];
126 | $data->slug = $this->request['api_product_id'];
127 | $data->new_version = get_post_meta( $api_product_post_id, '_version', true );
128 | $data->url = get_post_meta( $api_product_post_id, '_plugin_uri', true );
129 | $data->package = wppl_get_package_download_url( $api_product_post_id, $this->request['licence_key'], $this->request['email'] );
130 |
131 | $this->send_response( $data );
132 | }
133 |
134 | /**
135 | * Get plugin information
136 | */
137 | public function plugin_information() {
138 | $this->check_access();
139 |
140 | $api_product_post_id = wppl_get_api_product_post_id( $this->request['api_product_id'] );
141 | $plugin_version = get_post_meta( $api_product_post_id, '_version', true );
142 | $transient_name = 'plugininfo_' . md5( $this->request['api_product_id'] . $plugin_version );
143 |
144 | if ( false === ( $data = get_transient( $transient_name ) ) ) {
145 |
146 | $api_product_post = get_post( $api_product_post_id );
147 | $data = new stdClass();
148 | $data->name = $api_product_post->post_title;
149 | $data->plugin = $this->request['plugin_name'];
150 | $data->slug = $this->request['api_product_id'];
151 | $data->version = $plugin_version;
152 | $data->last_updated = get_post_meta( $api_product_post_id, '_last_updated', true );
153 |
154 | if ( $author_uri = get_post_meta( $api_product_post_id, '_author_uri', true ) ) {
155 | $data->author = '' . get_post_meta( $api_product_post_id, '_author', true ) . '';
156 | } else {
157 | $data->author = get_post_meta( $api_product_post_id, '_author', true );
158 | }
159 |
160 | $data->requires = get_post_meta( $api_product_post_id, '_requires_wp_version', true );
161 | $data->tested = get_post_meta( $api_product_post_id, '_tested_wp_version', true );
162 | $data->homepage = get_post_meta( $api_product_post_id, '_plugin_uri', true );
163 | $data->sections = array(
164 | 'description' => wpautop( $api_product_post->post_content ),
165 | 'changelog' => get_post_meta( $api_product_post_id, '_changelog', true )
166 | );
167 |
168 | if ( ! function_exists( 'Markdown' ) ) {
169 | include_once( 'markdown.php' );
170 | }
171 |
172 | foreach ( $data->sections as $key => $section ) {
173 | $data->sections[ $key ] = str_replace( array( "\r\n", "\r"), "\n", $data->sections[ $key ] );
174 | $data->sections[ $key ] = trim( $data->sections[ $key ] );
175 | if ( 0 === strpos( $data->sections[ $key ], "\xEF\xBB\xBF" ) ) {
176 | $data->sections[ $key ] = substr( $data->sections[ $key ], 3 );
177 | }
178 | // Markdown transformations
179 | $data->sections[ $key ] = preg_replace('/^[\s]*=[\s]+(.+?)[\s]+=/m', '
'.$text.'
'; 145 | $text = preg_replace('{\n{2,}}', "\n\n", $text); 146 | } 147 | return $text; 148 | } 149 | 150 | function mdwp_strip_p($t) { return preg_replace('{?p>}i', '', $t); } 151 | 152 | function mdwp_hide_tags($text) { 153 | global $mdwp_hidden_tags, $mdwp_placeholders; 154 | return str_replace($mdwp_hidden_tags, $mdwp_placeholders, $text); 155 | } 156 | function mdwp_show_tags($text) { 157 | global $mdwp_hidden_tags, $mdwp_placeholders; 158 | return str_replace($mdwp_placeholders, $mdwp_hidden_tags, $text); 159 | } 160 | } 161 | 162 | 163 | ### bBlog Plugin Info ### 164 | 165 | function identify_modifier_markdown() { 166 | return array( 167 | 'name' => 'markdown', 168 | 'type' => 'modifier', 169 | 'nicename' => 'PHP Markdown Extra', 170 | 'description' => 'A text-to-HTML conversion tool for web writers', 171 | 'authors' => 'Michel Fortin and John Gruber', 172 | 'licence' => 'GPL', 173 | 'version' => MARKDOWNEXTRA_VERSION, 174 | 'help' => 'Markdown syntax allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by John Gruber. More...', 175 | ); 176 | } 177 | 178 | 179 | ### Smarty Modifier Interface ### 180 | 181 | function smarty_modifier_markdown($text) { 182 | return Markdown($text); 183 | } 184 | 185 | 186 | ### Textile Compatibility Mode ### 187 | 188 | # Rename this file to "classTextile.php" and it can replace Textile everywhere. 189 | 190 | if (strcasecmp(substr(__FILE__, -16), "classTextile.php") == 0) { 191 | # Try to include PHP SmartyPants. Should be in the same directory. 192 | @include_once 'smartypants.php'; 193 | # Fake Textile class. It calls Markdown instead. 194 | class Textile { 195 | function TextileThis($text, $lite='', $encode='') { 196 | if ($lite == '' && $encode == '') $text = Markdown($text); 197 | if (function_exists('SmartyPants')) $text = SmartyPants($text); 198 | return $text; 199 | } 200 | # Fake restricted version: restrictions are not supported for now. 201 | function TextileRestricted($text, $lite='', $noimage='') { 202 | return $this->TextileThis($text, $lite); 203 | } 204 | # Workaround to ensure compatibility with TextPattern 4.0.3. 205 | function blockLite($text) { return $text; } 206 | } 207 | } 208 | 209 | 210 | 211 | # 212 | # Markdown Parser Class 213 | # 214 | 215 | class Markdown_Parser { 216 | 217 | # Regex to match balanced [brackets]. 218 | # Needed to insert a maximum bracked depth while converting to PHP. 219 | var $nested_brackets_depth = 6; 220 | var $nested_brackets_re; 221 | 222 | var $nested_url_parenthesis_depth = 4; 223 | var $nested_url_parenthesis_re; 224 | 225 | # Table of hash values for escaped characters: 226 | var $escape_chars = '\`*_{}[]()>#+-.!'; 227 | var $escape_chars_re; 228 | 229 | # Change to ">" for HTML output. 230 | var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX; 231 | var $tab_width = MARKDOWN_TAB_WIDTH; 232 | 233 | # Change to `true` to disallow markup or entities. 234 | var $no_markup = false; 235 | var $no_entities = false; 236 | 237 | # Predefined urls and titles for reference links and images. 238 | var $predef_urls = array(); 239 | var $predef_titles = array(); 240 | 241 | 242 | function Markdown_Parser() { 243 | # 244 | # Constructor function. Initialize appropriate member variables. 245 | # 246 | $this->_initDetab(); 247 | $this->prepareItalicsAndBold(); 248 | 249 | $this->nested_brackets_re = 250 | str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth). 251 | str_repeat('\])*', $this->nested_brackets_depth); 252 | 253 | $this->nested_url_parenthesis_re = 254 | str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth). 255 | str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth); 256 | 257 | $this->escape_chars_re = '['.preg_quote($this->escape_chars).']'; 258 | 259 | # Sort document, block, and span gamut in ascendent priority order. 260 | asort($this->document_gamut); 261 | asort($this->block_gamut); 262 | asort($this->span_gamut); 263 | } 264 | 265 | 266 | # Internal hashes used during transformation. 267 | var $urls = array(); 268 | var $titles = array(); 269 | var $html_hashes = array(); 270 | 271 | # Status flag to avoid invalid nesting. 272 | var $in_anchor = false; 273 | 274 | 275 | function setup() { 276 | # 277 | # Called before the transformation process starts to setup parser 278 | # states. 279 | # 280 | # Clear global hashes. 281 | $this->urls = $this->predef_urls; 282 | $this->titles = $this->predef_titles; 283 | $this->html_hashes = array(); 284 | 285 | $in_anchor = false; 286 | } 287 | 288 | function teardown() { 289 | # 290 | # Called after the transformation process to clear any variable 291 | # which may be taking up memory unnecessarly. 292 | # 293 | $this->urls = array(); 294 | $this->titles = array(); 295 | $this->html_hashes = array(); 296 | } 297 | 298 | 299 | function transform($text) { 300 | # 301 | # Main function. Performs some preprocessing on the input text 302 | # and pass it through the document gamut. 303 | # 304 | $this->setup(); 305 | 306 | # Remove UTF-8 BOM and marker character in input, if present. 307 | $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text); 308 | 309 | # Standardize line endings: 310 | # DOS to Unix and Mac to Unix 311 | $text = preg_replace('{\r\n?}', "\n", $text); 312 | 313 | # Make sure $text ends with a couple of newlines: 314 | $text .= "\n\n"; 315 | 316 | # Convert all tabs to spaces. 317 | $text = $this->detab($text); 318 | 319 | # Turn block-level HTML blocks into hash entries 320 | $text = $this->hashHTMLBlocks($text); 321 | 322 | # Strip any lines consisting only of spaces and tabs. 323 | # This makes subsequent regexen easier to write, because we can 324 | # match consecutive blank lines with /\n+/ instead of something 325 | # contorted like /[ ]*\n+/ . 326 | $text = preg_replace('/^[ ]+$/m', '', $text); 327 | 328 | # Run document gamut methods. 329 | foreach ($this->document_gamut as $method => $priority) { 330 | $text = $this->$method($text); 331 | } 332 | 333 | $this->teardown(); 334 | 335 | return $text . "\n"; 336 | } 337 | 338 | var $document_gamut = array( 339 | # Strip link definitions, store in hashes. 340 | "stripLinkDefinitions" => 20, 341 | 342 | "runBasicBlockGamut" => 30, 343 | ); 344 | 345 | 346 | function stripLinkDefinitions($text) { 347 | # 348 | # Strips link definitions from text, stores the URLs and titles in 349 | # hash references. 350 | # 351 | $less_than_tab = $this->tab_width - 1; 352 | 353 | # Link defs are in the form: ^[id]: url "optional title" 354 | $text = preg_replace_callback('{ 355 | ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1 356 | [ ]* 357 | \n? # maybe *one* newline 358 | [ ]* 359 | (?: 360 | <(.+?)> # url = $2 361 | | 362 | (\S+?) # url = $3 363 | ) 364 | [ ]* 365 | \n? # maybe one newline 366 | [ ]* 367 | (?: 368 | (?<=\s) # lookbehind for whitespace 369 | ["(] 370 | (.*?) # title = $4 371 | [")] 372 | [ ]* 373 | )? # title is optional 374 | (?:\n+|\Z) 375 | }xm', 376 | array(&$this, '_stripLinkDefinitions_callback'), 377 | $text); 378 | return $text; 379 | } 380 | function _stripLinkDefinitions_callback($matches) { 381 | $link_id = strtolower($matches[1]); 382 | $url = $matches[2] == '' ? $matches[3] : $matches[2]; 383 | $this->urls[$link_id] = $url; 384 | $this->titles[$link_id] =& $matches[4]; 385 | return ''; # String that will replace the block 386 | } 387 | 388 | 389 | function hashHTMLBlocks($text) { 390 | if ($this->no_markup) return $text; 391 | 392 | $less_than_tab = $this->tab_width - 1; 393 | 394 | # Hashify HTML blocks: 395 | # We only want to do this for block-level HTML tags, such as headers, 396 | # lists, and tables. That's because we still want to wrap
s around 397 | # "paragraphs" that are wrapped in non-block-level tags, such as anchors, 398 | # phrase emphasis, and spans. The list of tags we're looking for is 399 | # hard-coded: 400 | # 401 | # * List "a" is made of tags which can be both inline or block-level. 402 | # These will be treated block-level when the start tag is alone on 403 | # its line, otherwise they're not matched here and will be taken as 404 | # inline later. 405 | # * List "b" is made of tags which are always block-level; 406 | # 407 | $block_tags_a_re = 'ins|del'; 408 | $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'. 409 | 'script|noscript|form|fieldset|iframe|math'; 410 | 411 | # Regular expression for the content of a block tag. 412 | $nested_tags_level = 4; 413 | $attr = ' 414 | (?> # optional tag attributes 415 | \s # starts with whitespace 416 | (?> 417 | [^>"/]+ # text outside quotes 418 | | 419 | /+(?!>) # slash not followed by ">" 420 | | 421 | "[^"]*" # text inside double quotes (tolerate ">") 422 | | 423 | \'[^\']*\' # text inside single quotes (tolerate ">") 424 | )* 425 | )? 426 | '; 427 | $content = 428 | str_repeat(' 429 | (?> 430 | [^<]+ # content without tag 431 | | 432 | <\2 # nested opening tag 433 | '.$attr.' # attributes 434 | (?> 435 | /> 436 | | 437 | >', $nested_tags_level). # end of opening tag 438 | '.*?'. # last level nested tag content 439 | str_repeat(' 440 | \2\s*> # closing nested tag 441 | ) 442 | | 443 | <(?!/\2\s*> # other tags with a different name 444 | ) 445 | )*', 446 | $nested_tags_level); 447 | $content2 = str_replace('\2', '\3', $content); 448 | 449 | # First, look for nested blocks, e.g.: 450 | #
` blocks.
1105 | #
1106 | $text = preg_replace_callback('{
1107 | (?:\n\n|\A\n?)
1108 | ( # $1 = the code block -- one or more lines, starting with a space/tab
1109 | (?>
1110 | [ ]{'.$this->tab_width.'} # Lines must start with a tab or a tab-width of spaces
1111 | .*\n+
1112 | )+
1113 | )
1114 | ((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z) # Lookahead for non-space at line-start, or end of doc
1115 | }xm',
1116 | array(&$this, '_doCodeBlocks_callback'), $text);
1117 |
1118 | return $text;
1119 | }
1120 | function _doCodeBlocks_callback($matches) {
1121 | $codeblock = $matches[1];
1122 |
1123 | $codeblock = $this->outdent($codeblock);
1124 | $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
1125 |
1126 | # trim leading newlines and trailing newlines
1127 | $codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
1128 |
1129 | $codeblock = "$codeblock\n
";
1130 | return "\n\n".$this->hashBlock($codeblock)."\n\n";
1131 | }
1132 |
1133 |
1134 | function makeCodeSpan($code) {
1135 | #
1136 | # Create a code span markup for $code. Called from handleSpanToken.
1137 | #
1138 | $code = htmlspecialchars(trim($code), ENT_NOQUOTES);
1139 | return $this->hashPart("$code
");
1140 | }
1141 |
1142 |
1143 | var $em_relist = array(
1144 | '' => '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? '(?:(? '(?<=\S|^)(? '(?<=\S|^)(?em_relist as $em => $em_re) {
1166 | foreach ($this->strong_relist as $strong => $strong_re) {
1167 | # Construct list of allowed token expressions.
1168 | $token_relist = array();
1169 | if (isset($this->em_strong_relist["$em$strong"])) {
1170 | $token_relist[] = $this->em_strong_relist["$em$strong"];
1171 | }
1172 | $token_relist[] = $em_re;
1173 | $token_relist[] = $strong_re;
1174 |
1175 | # Construct master expression from list.
1176 | $token_re = '{('. implode('|', $token_relist) .')}';
1177 | $this->em_strong_prepared_relist["$em$strong"] = $token_re;
1178 | }
1179 | }
1180 | }
1181 |
1182 | function doItalicsAndBold($text) {
1183 | $token_stack = array('');
1184 | $text_stack = array('');
1185 | $em = '';
1186 | $strong = '';
1187 | $tree_char_em = false;
1188 |
1189 | while (1) {
1190 | #
1191 | # Get prepared regular expression for seraching emphasis tokens
1192 | # in current context.
1193 | #
1194 | $token_re = $this->em_strong_prepared_relist["$em$strong"];
1195 |
1196 | #
1197 | # Each loop iteration search for the next emphasis token.
1198 | # Each token is then passed to handleSpanToken.
1199 | #
1200 | $parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
1201 | $text_stack[0] .= $parts[0];
1202 | $token =& $parts[1];
1203 | $text =& $parts[2];
1204 |
1205 | if (empty($token)) {
1206 | # Reached end of text span: empty stack without emitting.
1207 | # any more emphasis.
1208 | while ($token_stack[0]) {
1209 | $text_stack[1] .= array_shift($token_stack);
1210 | $text_stack[0] .= array_shift($text_stack);
1211 | }
1212 | break;
1213 | }
1214 |
1215 | $token_len = strlen($token);
1216 | if ($tree_char_em) {
1217 | # Reached closing marker while inside a three-char emphasis.
1218 | if ($token_len == 3) {
1219 | # Three-char closing marker, close em and strong.
1220 | array_shift($token_stack);
1221 | $span = array_shift($text_stack);
1222 | $span = $this->runSpanGamut($span);
1223 | $span = "$span";
1224 | $text_stack[0] .= $this->hashPart($span);
1225 | $em = '';
1226 | $strong = '';
1227 | } else {
1228 | # Other closing marker: close one em or strong and
1229 | # change current token state to match the other
1230 | $token_stack[0] = str_repeat($token{0}, 3-$token_len);
1231 | $tag = $token_len == 2 ? "strong" : "em";
1232 | $span = $text_stack[0];
1233 | $span = $this->runSpanGamut($span);
1234 | $span = "<$tag>$span$tag>";
1235 | $text_stack[0] = $this->hashPart($span);
1236 | $$tag = ''; # $$tag stands for $em or $strong
1237 | }
1238 | $tree_char_em = false;
1239 | } else if ($token_len == 3) {
1240 | if ($em) {
1241 | # Reached closing marker for both em and strong.
1242 | # Closing strong marker:
1243 | for ($i = 0; $i < 2; ++$i) {
1244 | $shifted_token = array_shift($token_stack);
1245 | $tag = strlen($shifted_token) == 2 ? "strong" : "em";
1246 | $span = array_shift($text_stack);
1247 | $span = $this->runSpanGamut($span);
1248 | $span = "<$tag>$span$tag>";
1249 | $text_stack[0] .= $this->hashPart($span);
1250 | $$tag = ''; # $$tag stands for $em or $strong
1251 | }
1252 | } else {
1253 | # Reached opening three-char emphasis marker. Push on token
1254 | # stack; will be handled by the special condition above.
1255 | $em = $token{0};
1256 | $strong = "$em$em";
1257 | array_unshift($token_stack, $token);
1258 | array_unshift($text_stack, '');
1259 | $tree_char_em = true;
1260 | }
1261 | } else if ($token_len == 2) {
1262 | if ($strong) {
1263 | # Unwind any dangling emphasis marker:
1264 | if (strlen($token_stack[0]) == 1) {
1265 | $text_stack[1] .= array_shift($token_stack);
1266 | $text_stack[0] .= array_shift($text_stack);
1267 | }
1268 | # Closing strong marker:
1269 | array_shift($token_stack);
1270 | $span = array_shift($text_stack);
1271 | $span = $this->runSpanGamut($span);
1272 | $span = "$span";
1273 | $text_stack[0] .= $this->hashPart($span);
1274 | $strong = '';
1275 | } else {
1276 | array_unshift($token_stack, $token);
1277 | array_unshift($text_stack, '');
1278 | $strong = $token;
1279 | }
1280 | } else {
1281 | # Here $token_len == 1
1282 | if ($em) {
1283 | if (strlen($token_stack[0]) == 1) {
1284 | # Closing emphasis marker:
1285 | array_shift($token_stack);
1286 | $span = array_shift($text_stack);
1287 | $span = $this->runSpanGamut($span);
1288 | $span = "$span";
1289 | $text_stack[0] .= $this->hashPart($span);
1290 | $em = '';
1291 | } else {
1292 | $text_stack[0] .= $token;
1293 | }
1294 | } else {
1295 | array_unshift($token_stack, $token);
1296 | array_unshift($text_stack, '');
1297 | $em = $token;
1298 | }
1299 | }
1300 | }
1301 | return $text_stack[0];
1302 | }
1303 |
1304 |
1305 | function doBlockQuotes($text) {
1306 | $text = preg_replace_callback('/
1307 | ( # Wrap whole match in $1
1308 | (?>
1309 | ^[ ]*>[ ]? # ">" at the start of a line
1310 | .+\n # rest of the first line
1311 | (.+\n)* # subsequent consecutive lines
1312 | \n* # blanks
1313 | )+
1314 | )
1315 | /xm',
1316 | array(&$this, '_doBlockQuotes_callback'), $text);
1317 |
1318 | return $text;
1319 | }
1320 | function _doBlockQuotes_callback($matches) {
1321 | $bq = $matches[1];
1322 | # trim one level of quoting - trim whitespace-only lines
1323 | $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
1324 | $bq = $this->runBlockGamut($bq); # recurse
1325 |
1326 | $bq = preg_replace('/^/m', " ", $bq);
1327 | # These leading spaces cause problem with content,
1328 | # so we need to fix that:
1329 | $bq = preg_replace_callback('{(\s*.+?
)}sx',
1330 | array(&$this, '_doBlockQuotes_callback2'), $bq);
1331 |
1332 | return "\n". $this->hashBlock("\n$bq\n
")."\n\n";
1333 | }
1334 | function _doBlockQuotes_callback2($matches) {
1335 | $pre = $matches[1];
1336 | $pre = preg_replace('/^ /m', '', $pre);
1337 | return $pre;
1338 | }
1339 |
1340 |
1341 | function formParagraphs($text) {
1342 | #
1343 | # Params:
1344 | # $text - string to process with html tags
1345 | #
1346 | # Strip leading and trailing lines:
1347 | $text = preg_replace('/\A\n+|\n+\z/', '', $text);
1348 |
1349 | $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
1350 |
1351 | #
1352 | # Wrap
tags and unhashify HTML blocks
1353 | #
1354 | foreach ($grafs as $key => $value) {
1355 | if (!preg_match('/^B\x1A[0-9]+B$/', $value)) {
1356 | # Is a paragraph.
1357 | $value = $this->runSpanGamut($value);
1358 | $value = preg_replace('/^([ ]*)/', "
", $value);
1359 | $value .= "
";
1360 | $grafs[$key] = $this->unhash($value);
1361 | }
1362 | else {
1363 | # Is a block.
1364 | # Modify elements of @grafs in-place...
1365 | $graf = $value;
1366 | $block = $this->html_hashes[$graf];
1367 | $graf = $block;
1368 | // if (preg_match('{
1369 | // \A
1370 | // ( # $1 = tag
1371 | // ]*
1373 | // \b
1374 | // markdown\s*=\s* ([\'"]) # $2 = attr quote char
1375 | // 1
1376 | // \2
1377 | // [^>]*
1378 | // >
1379 | // )
1380 | // ( # $3 = contents
1381 | // .*
1382 | // )
1383 | // () # $4 = closing tag
1384 | // \z
1385 | // }xs', $block, $matches))
1386 | // {
1387 | // list(, $div_open, , $div_content, $div_close) = $matches;
1388 | //
1389 | // # We can't call Markdown(), because that resets the hash;
1390 | // # that initialization code should be pulled into its own sub, though.
1391 | // $div_content = $this->hashHTMLBlocks($div_content);
1392 | //
1393 | // # Run document gamut methods on the content.
1394 | // foreach ($this->document_gamut as $method => $priority) {
1395 | // $div_content = $this->$method($div_content);
1396 | // }
1397 | //
1398 | // $div_open = preg_replace(
1399 | // '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
1400 | //
1401 | // $graf = $div_open . "\n" . $div_content . "\n" . $div_close;
1402 | // }
1403 | $grafs[$key] = $graf;
1404 | }
1405 | }
1406 |
1407 | return implode("\n\n", $grafs);
1408 | }
1409 |
1410 |
1411 | function encodeAttribute($text) {
1412 | #
1413 | # Encode text for a double-quoted HTML attribute. This function
1414 | # is *not* suitable for attributes enclosed in single quotes.
1415 | #
1416 | $text = $this->encodeAmpsAndAngles($text);
1417 | $text = str_replace('"', '"', $text);
1418 | return $text;
1419 | }
1420 |
1421 |
1422 | function encodeAmpsAndAngles($text) {
1423 | #
1424 | # Smart processing for ampersands and angle brackets that need to
1425 | # be encoded. Valid character entities are left alone unless the
1426 | # no-entities mode is set.
1427 | #
1428 | if ($this->no_entities) {
1429 | $text = str_replace('&', '&', $text);
1430 | } else {
1431 | # Ampersand-encoding based entirely on Nat Irons's Amputator
1432 | # MT plugin:
1433 | $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/',
1434 | '&', $text);;
1435 | }
1436 | # Encode remaining <'s
1437 | $text = str_replace('<', '<', $text);
1438 |
1439 | return $text;
1440 | }
1441 |
1442 |
1443 | function doAutoLinks($text) {
1444 | $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i',
1445 | array(&$this, '_doAutoLinks_url_callback'), $text);
1446 |
1447 | # Email addresses:
1448 | $text = preg_replace_callback('{
1449 | <
1450 | (?:mailto:)?
1451 | (
1452 | (?:
1453 | [-!#$%&\'*+/=?^_`.{|}~\w\x80-\xFF]+
1454 | |
1455 | ".*?"
1456 | )
1457 | \@
1458 | (?:
1459 | [-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
1460 | |
1461 | \[[\d.a-fA-F:]+\] # IPv4 & IPv6
1462 | )
1463 | )
1464 | >
1465 | }xi',
1466 | array(&$this, '_doAutoLinks_email_callback'), $text);
1467 |
1468 | return $text;
1469 | }
1470 | function _doAutoLinks_url_callback($matches) {
1471 | $url = $this->encodeAttribute($matches[1]);
1472 | $link = "$url";
1473 | return $this->hashPart($link);
1474 | }
1475 | function _doAutoLinks_email_callback($matches) {
1476 | $address = $matches[1];
1477 | $link = $this->encodeEmailAddress($address);
1478 | return $this->hashPart($link);
1479 | }
1480 |
1481 |
1482 | function encodeEmailAddress($addr) {
1483 | #
1484 | # Input: an email address, e.g. "foo@example.com"
1485 | #
1486 | # Output: the email address as a mailto link, with each character
1487 | # of the address encoded as either a decimal or hex entity, in
1488 | # the hopes of foiling most address harvesting spam bots. E.g.:
1489 | #
1490 | #
1494 | #
1495 | # Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
1496 | # With some optimizations by Milian Wolff.
1497 | #
1498 | $addr = "mailto:" . $addr;
1499 | $chars = preg_split('/(? $char) {
1503 | $ord = ord($char);
1504 | # Ignore non-ascii chars.
1505 | if ($ord < 128) {
1506 | $r = ($seed * (1 + $key)) % 100; # Pseudo-random function.
1507 | # roughly 10% raw, 45% hex, 45% dec
1508 | # '@' *must* be encoded. I insist.
1509 | if ($r > 90 && $char != '@') /* do nothing */;
1510 | else if ($r < 45) $chars[$key] = ''.dechex($ord).';';
1511 | else $chars[$key] = ''.$ord.';';
1512 | }
1513 | }
1514 |
1515 | $addr = implode('', $chars);
1516 | $text = implode('', array_slice($chars, 7)); # text without `mailto:`
1517 | $addr = "$text";
1518 |
1519 | return $addr;
1520 | }
1521 |
1522 |
1523 | function parseSpan($str) {
1524 | #
1525 | # Take the string $str and parse it into tokens, hashing embeded HTML,
1526 | # escaped characters and handling code spans.
1527 | #
1528 | $output = '';
1529 |
1530 | $span_re = '{
1531 | (
1532 | \\\\'.$this->escape_chars_re.'
1533 | |
1534 | (?no_markup ? '' : '
1537 | |
1538 | # comment
1539 | |
1540 | <\?.*?\?> | <%.*?%> # processing instruction
1541 | |
1542 | <[/!$]?[-a-zA-Z0-9:_]+ # regular tags
1543 | (?>
1544 | \s
1545 | (?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
1546 | )?
1547 | >
1548 | ').'
1549 | )
1550 | }xs';
1551 |
1552 | while (1) {
1553 | #
1554 | # Each loop iteration seach for either the next tag, the next
1555 | # openning code span marker, or the next escaped character.
1556 | # Each token is then passed to handleSpanToken.
1557 | #
1558 | $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE);
1559 |
1560 | # Create token from text preceding tag.
1561 | if ($parts[0] != "") {
1562 | $output .= $parts[0];
1563 | }
1564 |
1565 | # Check if we reach the end.
1566 | if (isset($parts[1])) {
1567 | $output .= $this->handleSpanToken($parts[1], $parts[2]);
1568 | $str = $parts[2];
1569 | }
1570 | else {
1571 | break;
1572 | }
1573 | }
1574 |
1575 | return $output;
1576 | }
1577 |
1578 |
1579 | function handleSpanToken($token, &$str) {
1580 | #
1581 | # Handle $token provided by parseSpan by determining its nature and
1582 | # returning the corresponding value that should replace it.
1583 | #
1584 | switch ($token{0}) {
1585 | case "\\":
1586 | return $this->hashPart("". ord($token{1}). ";");
1587 | case "`":
1588 | # Search for end marker in remaining text.
1589 | if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm',
1590 | $str, $matches))
1591 | {
1592 | $str = $matches[2];
1593 | $codespan = $this->makeCodeSpan($matches[1]);
1594 | return $this->hashPart($codespan);
1595 | }
1596 | return $token; // return as text since no ending marker found.
1597 | default:
1598 | return $this->hashPart($token);
1599 | }
1600 | }
1601 |
1602 |
1603 | function outdent($text) {
1604 | #
1605 | # Remove one level of line-leading tabs or spaces
1606 | #
1607 | return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text);
1608 | }
1609 |
1610 |
1611 | # String length function for detab. `_initDetab` will create a function to
1612 | # hanlde UTF-8 if the default function does not exist.
1613 | var $utf8_strlen = 'mb_strlen';
1614 |
1615 | function detab($text) {
1616 | #
1617 | # Replace tabs with the appropriate amount of space.
1618 | #
1619 | # For each line we separate the line in blocks delemited by
1620 | # tab characters. Then we reconstruct every line by adding the
1621 | # appropriate number of space between each blocks.
1622 |
1623 | $text = preg_replace_callback('/^.*\t.*$/m',
1624 | array(&$this, '_detab_callback'), $text);
1625 |
1626 | return $text;
1627 | }
1628 | function _detab_callback($matches) {
1629 | $line = $matches[0];
1630 | $strlen = $this->utf8_strlen; # strlen function for UTF-8.
1631 |
1632 | # Split in blocks.
1633 | $blocks = explode("\t", $line);
1634 | # Add each blocks to the line.
1635 | $line = $blocks[0];
1636 | unset($blocks[0]); # Do not add first block twice.
1637 | foreach ($blocks as $block) {
1638 | # Calculate amount of space, insert spaces, insert block.
1639 | $amount = $this->tab_width -
1640 | $strlen($line, 'UTF-8') % $this->tab_width;
1641 | $line .= str_repeat(" ", $amount) . $block;
1642 | }
1643 | return $line;
1644 | }
1645 | function _initDetab() {
1646 | #
1647 | # Check for the availability of the function in the `utf8_strlen` property
1648 | # (initially `mb_strlen`). If the function is not available, create a
1649 | # function that will loosely count the number of UTF-8 characters with a
1650 | # regular expression.
1651 | #
1652 | if (function_exists($this->utf8_strlen)) return;
1653 | $this->utf8_strlen = create_function('$text', 'return preg_match_all(
1654 | "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/",
1655 | $text, $m);');
1656 | }
1657 |
1658 |
1659 | function unhash($text) {
1660 | #
1661 | # Swap back in all the tags hashed by _HashHTMLBlocks.
1662 | #
1663 | return preg_replace_callback('/(.)\x1A[0-9]+\1/',
1664 | array(&$this, '_unhash_callback'), $text);
1665 | }
1666 | function _unhash_callback($matches) {
1667 | return $this->html_hashes[$matches[0]];
1668 | }
1669 |
1670 | }
1671 |
1672 |
1673 | #
1674 | # Markdown Extra Parser Class
1675 | #
1676 |
1677 | class MarkdownExtra_Parser extends Markdown_Parser {
1678 |
1679 | # Prefix for footnote ids.
1680 | var $fn_id_prefix = "";
1681 |
1682 | # Optional title attribute for footnote links and backlinks.
1683 | var $fn_link_title = MARKDOWN_FN_LINK_TITLE;
1684 | var $fn_backlink_title = MARKDOWN_FN_BACKLINK_TITLE;
1685 |
1686 | # Optional class attribute for footnote links and backlinks.
1687 | var $fn_link_class = MARKDOWN_FN_LINK_CLASS;
1688 | var $fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS;
1689 |
1690 | # Predefined abbreviations.
1691 | var $predef_abbr = array();
1692 |
1693 |
1694 | function MarkdownExtra_Parser() {
1695 | #
1696 | # Constructor function. Initialize the parser object.
1697 | #
1698 | # Add extra escapable characters before parent constructor
1699 | # initialize the table.
1700 | $this->escape_chars .= ':|';
1701 |
1702 | # Insert extra document, block, and span transformations.
1703 | # Parent constructor will do the sorting.
1704 | $this->document_gamut += array(
1705 | "doFencedCodeBlocks" => 5,
1706 | "stripFootnotes" => 15,
1707 | "stripAbbreviations" => 25,
1708 | "appendFootnotes" => 50,
1709 | );
1710 | $this->block_gamut += array(
1711 | "doFencedCodeBlocks" => 5,
1712 | "doTables" => 15,
1713 | "doDefLists" => 45,
1714 | );
1715 | $this->span_gamut += array(
1716 | "doFootnotes" => 5,
1717 | "doAbbreviations" => 70,
1718 | );
1719 |
1720 | parent::Markdown_Parser();
1721 | }
1722 |
1723 |
1724 | # Extra variables used during extra transformations.
1725 | var $footnotes = array();
1726 | var $footnotes_ordered = array();
1727 | var $abbr_desciptions = array();
1728 | var $abbr_word_re = '';
1729 |
1730 | # Give the current footnote number.
1731 | var $footnote_counter = 1;
1732 |
1733 |
1734 | function setup() {
1735 | #
1736 | # Setting up Extra-specific variables.
1737 | #
1738 | parent::setup();
1739 |
1740 | $this->footnotes = array();
1741 | $this->footnotes_ordered = array();
1742 | $this->abbr_desciptions = array();
1743 | $this->abbr_word_re = '';
1744 | $this->footnote_counter = 1;
1745 |
1746 | foreach ($this->predef_abbr as $abbr_word => $abbr_desc) {
1747 | if ($this->abbr_word_re)
1748 | $this->abbr_word_re .= '|';
1749 | $this->abbr_word_re .= preg_quote($abbr_word);
1750 | $this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
1751 | }
1752 | }
1753 |
1754 | function teardown() {
1755 | #
1756 | # Clearing Extra-specific variables.
1757 | #
1758 | $this->footnotes = array();
1759 | $this->footnotes_ordered = array();
1760 | $this->abbr_desciptions = array();
1761 | $this->abbr_word_re = '';
1762 |
1763 | parent::teardown();
1764 | }
1765 |
1766 |
1767 | ### HTML Block Parser ###
1768 |
1769 | # Tags that are always treated as block tags:
1770 | var $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend';
1771 |
1772 | # Tags treated as block tags only if the opening tag is alone on it's line:
1773 | var $context_block_tags_re = 'script|noscript|math|ins|del';
1774 |
1775 | # Tags where markdown="1" default to span mode:
1776 | var $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address';
1777 |
1778 | # Tags which must not have their contents modified, no matter where
1779 | # they appear:
1780 | var $clean_tags_re = 'script|math';
1781 |
1782 | # Tags that do not need to be closed.
1783 | var $auto_close_tags_re = 'hr|img';
1784 |
1785 |
1786 | function hashHTMLBlocks($text) {
1787 | #
1788 | # Hashify HTML Blocks and "clean tags".
1789 | #
1790 | # We only want to do this for block-level HTML tags, such as headers,
1791 | # lists, and tables. That's because we still want to wrap s around
1792 | # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
1793 | # phrase emphasis, and spans. The list of tags we're looking for is
1794 | # hard-coded.
1795 | #
1796 | # This works by calling _HashHTMLBlocks_InMarkdown, which then calls
1797 | # _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1"
1798 | # attribute is found whitin a tag, _HashHTMLBlocks_InHTML calls back
1799 | # _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag.
1800 | # These two functions are calling each other. It's recursive!
1801 | #
1802 | #
1803 | # Call the HTML-in-Markdown hasher.
1804 | #
1805 | list($text, ) = $this->_hashHTMLBlocks_inMarkdown($text);
1806 |
1807 | return $text;
1808 | }
1809 | function _hashHTMLBlocks_inMarkdown($text, $indent = 0,
1810 | $enclosing_tag_re = '', $span = false)
1811 | {
1812 | #
1813 | # Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags.
1814 | #
1815 | # * $indent is the number of space to be ignored when checking for code
1816 | # blocks. This is important because if we don't take the indent into
1817 | # account, something like this (which looks right) won't work as expected:
1818 | #
1819 | #
1820 | #
1821 | # Hello World. <-- Is this a Markdown code block or text?
1822 | # <-- Is this a Markdown code block or a real tag?
1823 | #
1824 | #
1825 | # If you don't like this, just don't indent the tag on which
1826 | # you apply the markdown="1" attribute.
1827 | #
1828 | # * If $enclosing_tag_re is not empty, stops at the first unmatched closing
1829 | # tag with that name. Nested tags supported.
1830 | #
1831 | # * If $span is true, text inside must treated as span. So any double
1832 | # newline will be replaced by a single newline so that it does not create
1833 | # paragraphs.
1834 | #
1835 | # Returns an array of that form: ( processed text , remaining text )
1836 | #
1837 | if ($text === '') return array('', '');
1838 |
1839 | # Regex to check for the presense of newlines around a block tag.
1840 | $newline_before_re = '/(?:^\n?|\n\n)*$/';
1841 | $newline_after_re =
1842 | '{
1843 | ^ # Start of text following the tag.
1844 | (?>[ ]*)? # Optional comment.
1845 | [ ]*\n # Must be followed by newline.
1846 | }xs';
1847 |
1848 | # Regex to match any tag.
1849 | $block_tag_re =
1850 | '{
1851 | ( # $2: Capture hole tag.
1852 | ? # Any opening or closing tag.
1853 | (?> # Tag name.
1854 | '.$this->block_tags_re.' |
1855 | '.$this->context_block_tags_re.' |
1856 | '.$this->clean_tags_re.' |
1857 | (?!\s)'.$enclosing_tag_re.'
1858 | )
1859 | (?:
1860 | (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name.
1861 | (?>
1862 | ".*?" | # Double quotes (can contain `>`)
1863 | \'.*?\' | # Single quotes (can contain `>`)
1864 | .+? # Anything but quotes and `>`.
1865 | )*?
1866 | )?
1867 | > # End of tag.
1868 | |
1869 | # HTML Comment
1870 | |
1871 | <\?.*?\?> | <%.*?%> # Processing instruction
1872 | |
1873 | # CData Block
1874 | |
1875 | # Code span marker
1876 | `+
1877 | '. ( !$span ? ' # If not in span.
1878 | |
1879 | # Indented code block
1880 | (?: ^[ ]*\n | ^ | \n[ ]*\n )
1881 | [ ]{'.($indent+4).'}[^\n]* \n
1882 | (?>
1883 | (?: [ ]{'.($indent+4).'}[^\n]* | [ ]* ) \n
1884 | )*
1885 | |
1886 | # Fenced code block marker
1887 | (?> ^ | \n )
1888 | [ ]{'.($indent).'}~~~+[ ]*\n
1889 | ' : '' ). ' # End (if not is span).
1890 | )
1891 | }xs';
1892 |
1893 |
1894 | $depth = 0; # Current depth inside the tag tree.
1895 | $parsed = ""; # Parsed text that will be returned.
1896 |
1897 | #
1898 | # Loop through every tag until we find the closing tag of the parent
1899 | # or loop until reaching the end of text if no parent tag specified.
1900 | #
1901 | do {
1902 | #
1903 | # Split the text using the first $tag_match pattern found.
1904 | # Text before pattern will be first in the array, text after
1905 | # pattern will be at the end, and between will be any catches made
1906 | # by the pattern.
1907 | #
1908 | $parts = preg_split($block_tag_re, $text, 2,
1909 | PREG_SPLIT_DELIM_CAPTURE);
1910 |
1911 | # If in Markdown span mode, add a empty-string span-level hash
1912 | # after each newline to prevent triggering any block element.
1913 | if ($span) {
1914 | $void = $this->hashPart("", ':');
1915 | $newline = "$void\n";
1916 | $parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void;
1917 | }
1918 |
1919 | $parsed .= $parts[0]; # Text before current tag.
1920 |
1921 | # If end of $text has been reached. Stop loop.
1922 | if (count($parts) < 3) {
1923 | $text = "";
1924 | break;
1925 | }
1926 |
1927 | $tag = $parts[1]; # Tag to handle.
1928 | $text = $parts[2]; # Remaining text after current tag.
1929 | $tag_re = preg_quote($tag); # For use in a regular expression.
1930 |
1931 | #
1932 | # Check for: Code span marker
1933 | #
1934 | if ($tag{0} == "`") {
1935 | # Find corresponding end marker.
1936 | $tag_re = preg_quote($tag);
1937 | if (preg_match('{^(?>.+?|\n(?!\n))*?(?.*\n)+?'.$tag_re.' *\n}', $text,
1964 | $matches))
1965 | {
1966 | # End marker found: pass text unchanged until marker.
1967 | $parsed .= $tag . $matches[0];
1968 | $text = substr($text, strlen($matches[0]));
1969 | }
1970 | else {
1971 | # No end marker: just skip it.
1972 | $parsed .= $tag;
1973 | }
1974 | }
1975 | #
1976 | # Check for: Opening Block level tag or
1977 | # Opening Context Block tag (like ins and del)
1978 | # used as a block tag (tag is alone on it's line).
1979 | #
1980 | else if (preg_match('{^<(?:'.$this->block_tags_re.')\b}', $tag) ||
1981 | ( preg_match('{^<(?:'.$this->context_block_tags_re.')\b}', $tag) &&
1982 | preg_match($newline_before_re, $parsed) &&
1983 | preg_match($newline_after_re, $text) )
1984 | )
1985 | {
1986 | # Need to parse tag and following text using the HTML parser.
1987 | list($block_text, $text) =
1988 | $this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true);
1989 |
1990 | # Make sure it stays outside of any paragraph by adding newlines.
1991 | $parsed .= "\n\n$block_text\n\n";
1992 | }
1993 | #
1994 | # Check for: Clean tag (like script, math)
1995 | # HTML Comments, processing instructions.
1996 | #
1997 | else if (preg_match('{^<(?:'.$this->clean_tags_re.')\b}', $tag) ||
1998 | $tag{1} == '!' || $tag{1} == '?')
1999 | {
2000 | # Need to parse tag and following text using the HTML parser.
2001 | # (don't check for markdown attribute)
2002 | list($block_text, $text) =
2003 | $this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false);
2004 |
2005 | $parsed .= $block_text;
2006 | }
2007 | #
2008 | # Check for: Tag with same name as enclosing tag.
2009 | #
2010 | else if ($enclosing_tag_re !== '' &&
2011 | # Same name as enclosing tag.
2012 | preg_match('{^?(?:'.$enclosing_tag_re.')\b}', $tag))
2013 | {
2014 | #
2015 | # Increase/decrease nested tag count.
2016 | #
2017 | if ($tag{1} == '/') $depth--;
2018 | else if ($tag{strlen($tag)-2} != '/') $depth++;
2019 |
2020 | if ($depth < 0) {
2021 | #
2022 | # Going out of parent element. Clean up and break so we
2023 | # return to the calling function.
2024 | #
2025 | $text = $tag . $text;
2026 | break;
2027 | }
2028 |
2029 | $parsed .= $tag;
2030 | }
2031 | else {
2032 | $parsed .= $tag;
2033 | }
2034 | } while ($depth >= 0);
2035 |
2036 | return array($parsed, $text);
2037 | }
2038 | function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) {
2039 | #
2040 | # Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags.
2041 | #
2042 | # * Calls $hash_method to convert any blocks.
2043 | # * Stops when the first opening tag closes.
2044 | # * $md_attr indicate if the use of the `markdown="1"` attribute is allowed.
2045 | # (it is not inside clean tags)
2046 | #
2047 | # Returns an array of that form: ( processed text , remaining text )
2048 | #
2049 | if ($text === '') return array('', '');
2050 |
2051 | # Regex to match `markdown` attribute inside of a tag.
2052 | $markdown_attr_re = '
2053 | {
2054 | \s* # Eat whitespace before the `markdown` attribute
2055 | markdown
2056 | \s*=\s*
2057 | (?>
2058 | (["\']) # $1: quote delimiter
2059 | (.*?) # $2: attribute value
2060 | \1 # matching delimiter
2061 | |
2062 | ([^\s>]*) # $3: unquoted attribute value
2063 | )
2064 | () # $4: make $3 always defined (avoid warnings)
2065 | }xs';
2066 |
2067 | # Regex to match any tag.
2068 | $tag_re = '{
2069 | ( # $2: Capture hole tag.
2070 | ? # Any opening or closing tag.
2071 | [\w:$]+ # Tag name.
2072 | (?:
2073 | (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name.
2074 | (?>
2075 | ".*?" | # Double quotes (can contain `>`)
2076 | \'.*?\' | # Single quotes (can contain `>`)
2077 | .+? # Anything but quotes and `>`.
2078 | )*?
2079 | )?
2080 | > # End of tag.
2081 | |
2082 | # HTML Comment
2083 | |
2084 | <\?.*?\?> | <%.*?%> # Processing instruction
2085 | |
2086 | # CData Block
2087 | )
2088 | }xs';
2089 |
2090 | $original_text = $text; # Save original text in case of faliure.
2091 |
2092 | $depth = 0; # Current depth inside the tag tree.
2093 | $block_text = ""; # Temporary text holder for current text.
2094 | $parsed = ""; # Parsed text that will be returned.
2095 |
2096 | #
2097 | # Get the name of the starting tag.
2098 | # (This pattern makes $base_tag_name_re safe without quoting.)
2099 | #
2100 | if (preg_match('/^<([\w:$]*)\b/', $text, $matches))
2101 | $base_tag_name_re = $matches[1];
2102 |
2103 | #
2104 | # Loop through every tag until we find the corresponding closing tag.
2105 | #
2106 | do {
2107 | #
2108 | # Split the text using the first $tag_match pattern found.
2109 | # Text before pattern will be first in the array, text after
2110 | # pattern will be at the end, and between will be any catches made
2111 | # by the pattern.
2112 | #
2113 | $parts = preg_split($tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
2114 |
2115 | if (count($parts) < 3) {
2116 | #
2117 | # End of $text reached with unbalenced tag(s).
2118 | # In that case, we return original text unchanged and pass the
2119 | # first character as filtered to prevent an infinite loop in the
2120 | # parent function.
2121 | #
2122 | return array($original_text{0}, substr($original_text, 1));
2123 | }
2124 |
2125 | $block_text .= $parts[0]; # Text before current tag.
2126 | $tag = $parts[1]; # Tag to handle.
2127 | $text = $parts[2]; # Remaining text after current tag.
2128 |
2129 | #
2130 | # Check for: Auto-close tag (like
)
2131 | # Comments and Processing Instructions.
2132 | #
2133 | if (preg_match('{^?(?:'.$this->auto_close_tags_re.')\b}', $tag) ||
2134 | $tag{1} == '!' || $tag{1} == '?')
2135 | {
2136 | # Just add the tag to the block as if it was text.
2137 | $block_text .= $tag;
2138 | }
2139 | else {
2140 | #
2141 | # Increase/decrease nested tag count. Only do so if
2142 | # the tag's name match base tag's.
2143 | #
2144 | if (preg_match('{^?'.$base_tag_name_re.'\b}', $tag)) {
2145 | if ($tag{1} == '/') $depth--;
2146 | else if ($tag{strlen($tag)-2} != '/') $depth++;
2147 | }
2148 |
2149 | #
2150 | # Check for `markdown="1"` attribute and handle it.
2151 | #
2152 | if ($md_attr &&
2153 | preg_match($markdown_attr_re, $tag, $attr_m) &&
2154 | preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3]))
2155 | {
2156 | # Remove `markdown` attribute from opening tag.
2157 | $tag = preg_replace($markdown_attr_re, '', $tag);
2158 |
2159 | # Check if text inside this tag must be parsed in span mode.
2160 | $this->mode = $attr_m[2] . $attr_m[3];
2161 | $span_mode = $this->mode == 'span' || $this->mode != 'block' &&
2162 | preg_match('{^<(?:'.$this->contain_span_tags_re.')\b}', $tag);
2163 |
2164 | # Calculate indent before tag.
2165 | if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) {
2166 | $strlen = $this->utf8_strlen;
2167 | $indent = $strlen($matches[1], 'UTF-8');
2168 | } else {
2169 | $indent = 0;
2170 | }
2171 |
2172 | # End preceding block with this tag.
2173 | $block_text .= $tag;
2174 | $parsed .= $this->$hash_method($block_text);
2175 |
2176 | # Get enclosing tag name for the ParseMarkdown function.
2177 | # (This pattern makes $tag_name_re safe without quoting.)
2178 | preg_match('/^<([\w:$]*)\b/', $tag, $matches);
2179 | $tag_name_re = $matches[1];
2180 |
2181 | # Parse the content using the HTML-in-Markdown parser.
2182 | list ($block_text, $text)
2183 | = $this->_hashHTMLBlocks_inMarkdown($text, $indent,
2184 | $tag_name_re, $span_mode);
2185 |
2186 | # Outdent markdown text.
2187 | if ($indent > 0) {
2188 | $block_text = preg_replace("/^[ ]{1,$indent}/m", "",
2189 | $block_text);
2190 | }
2191 |
2192 | # Append tag content to parsed text.
2193 | if (!$span_mode) $parsed .= "\n\n$block_text\n\n";
2194 | else $parsed .= "$block_text";
2195 |
2196 | # Start over a new block.
2197 | $block_text = "";
2198 | }
2199 | else $block_text .= $tag;
2200 | }
2201 |
2202 | } while ($depth > 0);
2203 |
2204 | #
2205 | # Hash last block text that wasn't processed inside the loop.
2206 | #
2207 | $parsed .= $this->$hash_method($block_text);
2208 |
2209 | return array($parsed, $text);
2210 | }
2211 |
2212 |
2213 | function hashClean($text) {
2214 | #
2215 | # Called whenever a tag must be hashed when a function insert a "clean" tag
2216 | # in $text, it pass through this function and is automaticaly escaped,
2217 | # blocking invalid nested overlap.
2218 | #
2219 | return $this->hashPart($text, 'C');
2220 | }
2221 |
2222 |
2223 | function doHeaders($text) {
2224 | #
2225 | # Redefined to add id attribute support.
2226 | #
2227 | # Setext-style headers:
2228 | # Header 1 {#header1}
2229 | # ========
2230 | #
2231 | # Header 2 {#header2}
2232 | # --------
2233 | #
2234 | $text = preg_replace_callback(
2235 | '{
2236 | (^.+?) # $1: Header text
2237 | (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # $2: Id attribute
2238 | [ ]*\n(=+|-+)[ ]*\n+ # $3: Header footer
2239 | }mx',
2240 | array(&$this, '_doHeaders_callback_setext'), $text);
2241 |
2242 | # atx-style headers:
2243 | # # Header 1 {#header1}
2244 | # ## Header 2 {#header2}
2245 | # ## Header 2 with closing hashes ## {#header3}
2246 | # ...
2247 | # ###### Header 6 {#header2}
2248 | #
2249 | $text = preg_replace_callback('{
2250 | ^(\#{1,6}) # $1 = string of #\'s
2251 | [ ]*
2252 | (.+?) # $2 = Header text
2253 | [ ]*
2254 | \#* # optional closing #\'s (not counted)
2255 | (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # id attribute
2256 | [ ]*
2257 | \n+
2258 | }xm',
2259 | array(&$this, '_doHeaders_callback_atx'), $text);
2260 |
2261 | return $text;
2262 | }
2263 | function _doHeaders_attr($attr) {
2264 | if (empty($attr)) return "";
2265 | return " id=\"$attr\"";
2266 | }
2267 | function _doHeaders_callback_setext($matches) {
2268 | if ($matches[3] == '-' && preg_match('{^- }', $matches[1]))
2269 | return $matches[0];
2270 | $level = $matches[3]{0} == '=' ? 1 : 2;
2271 | $attr = $this->_doHeaders_attr($id =& $matches[2]);
2272 | $block = "".$this->runSpanGamut($matches[1])." ";
2273 | return "\n" . $this->hashBlock($block) . "\n\n";
2274 | }
2275 | function _doHeaders_callback_atx($matches) {
2276 | $level = strlen($matches[1]);
2277 | $attr = $this->_doHeaders_attr($id =& $matches[3]);
2278 | $block = "".$this->runSpanGamut($matches[2])." ";
2279 | return "\n" . $this->hashBlock($block) . "\n\n";
2280 | }
2281 |
2282 |
2283 | function doTables($text) {
2284 | #
2285 | # Form HTML tables.
2286 | #
2287 | $less_than_tab = $this->tab_width - 1;
2288 | #
2289 | # Find tables with leading pipe.
2290 | #
2291 | # | Header 1 | Header 2
2292 | # | -------- | --------
2293 | # | Cell 1 | Cell 2
2294 | # | Cell 3 | Cell 4
2295 | #
2296 | $text = preg_replace_callback('
2297 | {
2298 | ^ # Start of a line
2299 | [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2300 | [|] # Optional leading pipe (present)
2301 | (.+) \n # $1: Header row (at least one pipe)
2302 |
2303 | [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2304 | [|] ([ ]*[-:]+[-| :]*) \n # $2: Header underline
2305 |
2306 | ( # $3: Cells
2307 | (?>
2308 | [ ]* # Allowed whitespace.
2309 | [|] .* \n # Row content.
2310 | )*
2311 | )
2312 | (?=\n|\Z) # Stop at final double newline.
2313 | }xm',
2314 | array(&$this, '_doTable_leadingPipe_callback'), $text);
2315 |
2316 | #
2317 | # Find tables without leading pipe.
2318 | #
2319 | # Header 1 | Header 2
2320 | # -------- | --------
2321 | # Cell 1 | Cell 2
2322 | # Cell 3 | Cell 4
2323 | #
2324 | $text = preg_replace_callback('
2325 | {
2326 | ^ # Start of a line
2327 | [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2328 | (\S.*[|].*) \n # $1: Header row (at least one pipe)
2329 |
2330 | [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2331 | ([-:]+[ ]*[|][-| :]*) \n # $2: Header underline
2332 |
2333 | ( # $3: Cells
2334 | (?>
2335 | .* [|] .* \n # Row content
2336 | )*
2337 | )
2338 | (?=\n|\Z) # Stop at final double newline.
2339 | }xm',
2340 | array(&$this, '_DoTable_callback'), $text);
2341 |
2342 | return $text;
2343 | }
2344 | function _doTable_leadingPipe_callback($matches) {
2345 | $head = $matches[1];
2346 | $underline = $matches[2];
2347 | $content = $matches[3];
2348 |
2349 | # Remove leading pipe for each row.
2350 | $content = preg_replace('/^ *[|]/m', '', $content);
2351 |
2352 | return $this->_doTable_callback(array($matches[0], $head, $underline, $content));
2353 | }
2354 | function _doTable_callback($matches) {
2355 | $head = $matches[1];
2356 | $underline = $matches[2];
2357 | $content = $matches[3];
2358 |
2359 | # Remove any tailing pipes for each line.
2360 | $head = preg_replace('/[|] *$/m', '', $head);
2361 | $underline = preg_replace('/[|] *$/m', '', $underline);
2362 | $content = preg_replace('/[|] *$/m', '', $content);
2363 |
2364 | # Reading alignement from header underline.
2365 | $separators = preg_split('/ *[|] */', $underline);
2366 | foreach ($separators as $n => $s) {
2367 | if (preg_match('/^ *-+: *$/', $s)) $attr[$n] = ' align="right"';
2368 | else if (preg_match('/^ *:-+: *$/', $s))$attr[$n] = ' align="center"';
2369 | else if (preg_match('/^ *:-+ *$/', $s)) $attr[$n] = ' align="left"';
2370 | else $attr[$n] = '';
2371 | }
2372 |
2373 | # Parsing span elements, including code spans, character escapes,
2374 | # and inline HTML tags, so that pipes inside those gets ignored.
2375 | $head = $this->parseSpan($head);
2376 | $headers = preg_split('/ *[|] */', $head);
2377 | $col_count = count($headers);
2378 |
2379 | # Write column headers.
2380 | $text = "\n";
2381 | $text .= "\n";
2382 | $text .= "\n";
2383 | foreach ($headers as $n => $header)
2384 | $text .= " ".$this->runSpanGamut(trim($header))." \n";
2385 | $text .= " \n";
2386 | $text .= "\n";
2387 |
2388 | # Split content by row.
2389 | $rows = explode("\n", trim($content, "\n"));
2390 |
2391 | $text .= "\n";
2392 | foreach ($rows as $row) {
2393 | # Parsing span elements, including code spans, character escapes,
2394 | # and inline HTML tags, so that pipes inside those gets ignored.
2395 | $row = $this->parseSpan($row);
2396 |
2397 | # Split row by cell.
2398 | $row_cells = preg_split('/ *[|] */', $row, $col_count);
2399 | $row_cells = array_pad($row_cells, $col_count, '');
2400 |
2401 | $text .= "\n";
2402 | foreach ($row_cells as $n => $cell)
2403 | $text .= " ".$this->runSpanGamut(trim($cell))." \n";
2404 | $text .= " \n";
2405 | }
2406 | $text .= "\n";
2407 | $text .= "
";
2408 |
2409 | return $this->hashBlock($text) . "\n";
2410 | }
2411 |
2412 |
2413 | function doDefLists($text) {
2414 | #
2415 | # Form HTML definition lists.
2416 | #
2417 | $less_than_tab = $this->tab_width - 1;
2418 |
2419 | # Re-usable pattern to match any entire dl list:
2420 | $whole_list_re = '(?>
2421 | ( # $1 = whole list
2422 | ( # $2
2423 | [ ]{0,'.$less_than_tab.'}
2424 | ((?>.*\S.*\n)+) # $3 = defined term
2425 | \n?
2426 | [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2427 | )
2428 | (?s:.+?)
2429 | ( # $4
2430 | \z
2431 | |
2432 | \n{2,}
2433 | (?=\S)
2434 | (?! # Negative lookahead for another term
2435 | [ ]{0,'.$less_than_tab.'}
2436 | (?: \S.*\n )+? # defined term
2437 | \n?
2438 | [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2439 | )
2440 | (?! # Negative lookahead for another definition
2441 | [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2442 | )
2443 | )
2444 | )
2445 | )'; // mx
2446 |
2447 | $text = preg_replace_callback('{
2448 | (?>\A\n?|(?<=\n\n))
2449 | '.$whole_list_re.'
2450 | }mx',
2451 | array(&$this, '_doDefLists_callback'), $text);
2452 |
2453 | return $text;
2454 | }
2455 | function _doDefLists_callback($matches) {
2456 | # Re-usable patterns to match list item bullets and number markers:
2457 | $list = $matches[1];
2458 |
2459 | # Turn double returns into triple returns, so that we can make a
2460 | # paragraph for the last item in a list, if necessary:
2461 | $result = trim($this->processDefListItems($list));
2462 | $result = "\n" . $result . "\n
";
2463 | return $this->hashBlock($result) . "\n\n";
2464 | }
2465 |
2466 |
2467 | function processDefListItems($list_str) {
2468 | #
2469 | # Process the contents of a single definition list, splitting it
2470 | # into individual term and definition list items.
2471 | #
2472 | $less_than_tab = $this->tab_width - 1;
2473 |
2474 | # trim trailing blank lines:
2475 | $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
2476 |
2477 | # Process definition terms.
2478 | $list_str = preg_replace_callback('{
2479 | (?>\A\n?|\n\n+) # leading line
2480 | ( # definition terms = $1
2481 | [ ]{0,'.$less_than_tab.'} # leading whitespace
2482 | (?![:][ ]|[ ]) # negative lookahead for a definition
2483 | # mark (colon) or more whitespace.
2484 | (?> \S.* \n)+? # actual term (not whitespace).
2485 | )
2486 | (?=\n?[ ]{0,3}:[ ]) # lookahead for following line feed
2487 | # with a definition mark.
2488 | }xm',
2489 | array(&$this, '_processDefListItems_callback_dt'), $list_str);
2490 |
2491 | # Process actual definitions.
2492 | $list_str = preg_replace_callback('{
2493 | \n(\n+)? # leading line = $1
2494 | ( # marker space = $2
2495 | [ ]{0,'.$less_than_tab.'} # whitespace before colon
2496 | [:][ ]+ # definition mark (colon)
2497 | )
2498 | ((?s:.+?)) # definition text = $3
2499 | (?= \n+ # stop at next definition mark,
2500 | (?: # next term or end of text
2501 | [ ]{0,'.$less_than_tab.'} [:][ ] |
2502 | | \z
2503 | )
2504 | )
2505 | }xm',
2506 | array(&$this, '_processDefListItems_callback_dd'), $list_str);
2507 |
2508 | return $list_str;
2509 | }
2510 | function _processDefListItems_callback_dt($matches) {
2511 | $terms = explode("\n", trim($matches[1]));
2512 | $text = '';
2513 | foreach ($terms as $term) {
2514 | $term = $this->runSpanGamut(trim($term));
2515 | $text .= "\n" . $term . " ";
2516 | }
2517 | return $text . "\n";
2518 | }
2519 | function _processDefListItems_callback_dd($matches) {
2520 | $leading_line = $matches[1];
2521 | $marker_space = $matches[2];
2522 | $def = $matches[3];
2523 |
2524 | if ($leading_line || preg_match('/\n{2,}/', $def)) {
2525 | # Replace marker with the appropriate whitespace indentation
2526 | $def = str_repeat(' ', strlen($marker_space)) . $def;
2527 | $def = $this->runBlockGamut($this->outdent($def . "\n\n"));
2528 | $def = "\n". $def ."\n";
2529 | }
2530 | else {
2531 | $def = rtrim($def);
2532 | $def = $this->runSpanGamut($this->outdent($def));
2533 | }
2534 |
2535 | return "\n " . $def . " \n";
2536 | }
2537 |
2538 |
2539 | function doFencedCodeBlocks($text) {
2540 | #
2541 | # Adding the fenced code block syntax to regular Markdown:
2542 | #
2543 | # ~~~
2544 | # Code block
2545 | # ~~~
2546 | #
2547 | $less_than_tab = $this->tab_width;
2548 |
2549 | $text = preg_replace_callback('{
2550 | (?:\n|\A)
2551 | # 1: Opening marker
2552 | (
2553 | ~{3,} # Marker: three tilde or more.
2554 | )
2555 | [ ]* \n # Whitespace and newline following marker.
2556 |
2557 | # 2: Content
2558 | (
2559 | (?>
2560 | (?!\1 [ ]* \n) # Not a closing marker.
2561 | .*\n+
2562 | )+
2563 | )
2564 |
2565 | # Closing marker.
2566 | \1 [ ]* \n
2567 | }xm',
2568 | array(&$this, '_doFencedCodeBlocks_callback'), $text);
2569 |
2570 | return $text;
2571 | }
2572 | function _doFencedCodeBlocks_callback($matches) {
2573 | $codeblock = $matches[2];
2574 | $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
2575 | $codeblock = preg_replace_callback('/^\n+/',
2576 | array(&$this, '_doFencedCodeBlocks_newlines'), $codeblock);
2577 | $codeblock = "$codeblock
";
2578 | return "\n\n".$this->hashBlock($codeblock)."\n\n";
2579 | }
2580 | function _doFencedCodeBlocks_newlines($matches) {
2581 | return str_repeat("
empty_element_suffix",
2582 | strlen($matches[0]));
2583 | }
2584 |
2585 |
2586 | #
2587 | # Redefining emphasis markers so that emphasis by underscore does not
2588 | # work in the middle of a word.
2589 | #
2590 | var $em_relist = array(
2591 | '' => '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? tags
2611 | #
2612 | # Strip leading and trailing lines:
2613 | $text = preg_replace('/\A\n+|\n+\z/', '', $text);
2614 |
2615 | $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
2616 |
2617 | #
2618 | # Wrap tags and unhashify HTML blocks
2619 | #
2620 | foreach ($grafs as $key => $value) {
2621 | $value = trim($this->runSpanGamut($value));
2622 |
2623 | # Check if this should be enclosed in a paragraph.
2624 | # Clean tag hashes & block tag hashes are left alone.
2625 | $is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value);
2626 |
2627 | if ($is_p) {
2628 | $value = "
$value
";
2629 | }
2630 | $grafs[$key] = $value;
2631 | }
2632 |
2633 | # Join grafs in one text, then unhash HTML tags.
2634 | $text = implode("\n\n", $grafs);
2635 |
2636 | # Finish by removing any tag hashes still present in $text.
2637 | $text = $this->unhash($text);
2638 |
2639 | return $text;
2640 | }
2641 |
2642 |
2643 | ### Footnotes
2644 |
2645 | function stripFootnotes($text) {
2646 | #
2647 | # Strips link definitions from text, stores the URLs and titles in
2648 | # hash references.
2649 | #
2650 | $less_than_tab = $this->tab_width - 1;
2651 |
2652 | # Link defs are in the form: [^id]: url "optional title"
2653 | $text = preg_replace_callback('{
2654 | ^[ ]{0,'.$less_than_tab.'}\[\^(.+?)\][ ]?: # note_id = $1
2655 | [ ]*
2656 | \n? # maybe *one* newline
2657 | ( # text = $2 (no blank lines allowed)
2658 | (?:
2659 | .+ # actual text
2660 | |
2661 | \n # newlines but
2662 | (?!\[\^.+?\]:\s)# negative lookahead for footnote marker.
2663 | (?!\n+[ ]{0,3}\S)# ensure line is not blank and followed
2664 | # by non-indented content
2665 | )*
2666 | )
2667 | }xm',
2668 | array(&$this, '_stripFootnotes_callback'),
2669 | $text);
2670 | return $text;
2671 | }
2672 | function _stripFootnotes_callback($matches) {
2673 | $note_id = $this->fn_id_prefix . $matches[1];
2674 | $this->footnotes[$note_id] = $this->outdent($matches[2]);
2675 | return ''; # String that will replace the block
2676 | }
2677 |
2678 |
2679 | function doFootnotes($text) {
2680 | #
2681 | # Replace footnote references in $text [^id] with a special text-token
2682 | # which will be replaced by the actual footnote marker in appendFootnotes.
2683 | #
2684 | if (!$this->in_anchor) {
2685 | $text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text);
2686 | }
2687 | return $text;
2688 | }
2689 |
2690 |
2691 | function appendFootnotes($text) {
2692 | #
2693 | # Append footnote list to text.
2694 | #
2695 | $text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}',
2696 | array(&$this, '_appendFootnotes_callback'), $text);
2697 |
2698 | if (!empty($this->footnotes_ordered)) {
2699 | $text .= "\n\n";
2700 | $text .= "\n";
2701 | $text .= "
empty_element_suffix ."\n";
2702 | $text .= "\n\n";
2703 |
2704 | $attr = " rev=\"footnote\"";
2705 | if ($this->fn_backlink_class != "") {
2706 | $class = $this->fn_backlink_class;
2707 | $class = $this->encodeAttribute($class);
2708 | $attr .= " class=\"$class\"";
2709 | }
2710 | if ($this->fn_backlink_title != "") {
2711 | $title = $this->fn_backlink_title;
2712 | $title = $this->encodeAttribute($title);
2713 | $attr .= " title=\"$title\"";
2714 | }
2715 | $num = 0;
2716 |
2717 | while (!empty($this->footnotes_ordered)) {
2718 | $footnote = reset($this->footnotes_ordered);
2719 | $note_id = key($this->footnotes_ordered);
2720 | unset($this->footnotes_ordered[$note_id]);
2721 |
2722 | $footnote .= "\n"; # Need to append newline before parsing.
2723 | $footnote = $this->runBlockGamut("$footnote\n");
2724 | $footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}',
2725 | array(&$this, '_appendFootnotes_callback'), $footnote);
2726 |
2727 | $attr = str_replace("%%", ++$num, $attr);
2728 | $note_id = $this->encodeAttribute($note_id);
2729 |
2730 | # Add backlink to last paragraph; create new paragraph if needed.
2731 | $backlink = "↩";
2732 | if (preg_match('{$}', $footnote)) {
2733 | $footnote = substr($footnote, 0, -4) . " $backlink";
2734 | } else {
2735 | $footnote .= "\n\n$backlink
";
2736 | }
2737 |
2738 | $text .= "- \n";
2739 | $text .= $footnote . "\n";
2740 | $text .= "
\n\n";
2741 | }
2742 |
2743 | $text .= "
\n";
2744 | $text .= "";
2745 | }
2746 | return $text;
2747 | }
2748 | function _appendFootnotes_callback($matches) {
2749 | $node_id = $this->fn_id_prefix . $matches[1];
2750 |
2751 | # Create footnote marker only if it has a corresponding footnote *and*
2752 | # the footnote hasn't been used by another marker.
2753 | if (isset($this->footnotes[$node_id])) {
2754 | # Transfert footnote content to the ordered list.
2755 | $this->footnotes_ordered[$node_id] = $this->footnotes[$node_id];
2756 | unset($this->footnotes[$node_id]);
2757 |
2758 | $num = $this->footnote_counter++;
2759 | $attr = " rel=\"footnote\"";
2760 | if ($this->fn_link_class != "") {
2761 | $class = $this->fn_link_class;
2762 | $class = $this->encodeAttribute($class);
2763 | $attr .= " class=\"$class\"";
2764 | }
2765 | if ($this->fn_link_title != "") {
2766 | $title = $this->fn_link_title;
2767 | $title = $this->encodeAttribute($title);
2768 | $attr .= " title=\"$title\"";
2769 | }
2770 |
2771 | $attr = str_replace("%%", $num, $attr);
2772 | $node_id = $this->encodeAttribute($node_id);
2773 |
2774 | return
2775 | "".
2776 | "$num".
2777 | "";
2778 | }
2779 |
2780 | return "[^".$matches[1]."]";
2781 | }
2782 |
2783 |
2784 | ### Abbreviations ###
2785 |
2786 | function stripAbbreviations($text) {
2787 | #
2788 | # Strips abbreviations from text, stores titles in hash references.
2789 | #
2790 | $less_than_tab = $this->tab_width - 1;
2791 |
2792 | # Link defs are in the form: [id]*: url "optional title"
2793 | $text = preg_replace_callback('{
2794 | ^[ ]{0,'.$less_than_tab.'}\*\[(.+?)\][ ]?: # abbr_id = $1
2795 | (.*) # text = $2 (no blank lines allowed)
2796 | }xm',
2797 | array(&$this, '_stripAbbreviations_callback'),
2798 | $text);
2799 | return $text;
2800 | }
2801 | function _stripAbbreviations_callback($matches) {
2802 | $abbr_word = $matches[1];
2803 | $abbr_desc = $matches[2];
2804 | if ($this->abbr_word_re)
2805 | $this->abbr_word_re .= '|';
2806 | $this->abbr_word_re .= preg_quote($abbr_word);
2807 | $this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
2808 | return ''; # String that will replace the block
2809 | }
2810 |
2811 |
2812 | function doAbbreviations($text) {
2813 | #
2814 | # Find defined abbreviations in text and wrap them in elements.
2815 | #
2816 | if ($this->abbr_word_re) {
2817 | // cannot use the /x modifier because abbr_word_re may
2818 | // contain significant spaces:
2819 | $text = preg_replace_callback('{'.
2820 | '(?abbr_word_re.')'.
2822 | '(?![\w\x1A])'.
2823 | '}',
2824 | array(&$this, '_doAbbreviations_callback'), $text);
2825 | }
2826 | return $text;
2827 | }
2828 | function _doAbbreviations_callback($matches) {
2829 | $abbr = $matches[0];
2830 | if (isset($this->abbr_desciptions[$abbr])) {
2831 | $desc = $this->abbr_desciptions[$abbr];
2832 | if (empty($desc)) {
2833 | return $this->hashPart("$abbr");
2834 | } else {
2835 | $desc = $this->encodeAttribute($desc);
2836 | return $this->hashPart("$abbr");
2837 | }
2838 | } else {
2839 | return $matches[0];
2840 | }
2841 | }
2842 |
2843 | }
2844 |
2845 |
2846 | /*
2847 |
2848 | PHP Markdown Extra
2849 | ==================
2850 |
2851 | Description
2852 | -----------
2853 |
2854 | This is a PHP port of the original Markdown formatter written in Perl
2855 | by John Gruber. This special "Extra" version of PHP Markdown features
2856 | further enhancements to the syntax for making additional constructs
2857 | such as tables and definition list.
2858 |
2859 | Markdown is a text-to-HTML filter; it translates an easy-to-read /
2860 | easy-to-write structured text format into HTML. Markdown's text format
2861 | is most similar to that of plain text email, and supports features such
2862 | as headers, *emphasis*, code blocks, blockquotes, and links.
2863 |
2864 | Markdown's syntax is designed not as a generic markup language, but
2865 | specifically to serve as a front-end to (X)HTML. You can use span-level
2866 | HTML tags anywhere in a Markdown document, and you can use block level
2867 | HTML tags (like and as well).
2868 |
2869 | For more information about Markdown's syntax, see:
2870 |
2871 |
2872 |
2873 |
2874 | Bugs
2875 | ----
2876 |
2877 | To file bug reports please send email to:
2878 |
2879 |
2880 |
2881 | Please include with your report: (1) the example input; (2) the output you
2882 | expected; (3) the output Markdown actually produced.
2883 |
2884 |
2885 | Version History
2886 | ---------------
2887 |
2888 | See the readme file for detailed release notes for this version.
2889 |
2890 |
2891 | Copyright and License
2892 | ---------------------
2893 |
2894 | PHP Markdown & Extra
2895 | Copyright (c) 2004-2009 Michel Fortin
2896 |
2897 | All rights reserved.
2898 |
2899 | Based on Markdown
2900 | Copyright (c) 2003-2006 John Gruber
2901 |
2902 | All rights reserved.
2903 |
2904 | Redistribution and use in source and binary forms, with or without
2905 | modification, are permitted provided that the following conditions are
2906 | met:
2907 |
2908 | * Redistributions of source code must retain the above copyright notice,
2909 | this list of conditions and the following disclaimer.
2910 |
2911 | * Redistributions in binary form must reproduce the above copyright
2912 | notice, this list of conditions and the following disclaimer in the
2913 | documentation and/or other materials provided with the distribution.
2914 |
2915 | * Neither the name "Markdown" nor the names of its contributors may
2916 | be used to endorse or promote products derived from this software
2917 | without specific prior written permission.
2918 |
2919 | This software is provided by the copyright holders and contributors "as
2920 | is" and any express or implied warranties, including, but not limited
2921 | to, the implied warranties of merchantability and fitness for a
2922 | particular purpose are disclaimed. In no event shall the copyright owner
2923 | or contributors be liable for any direct, indirect, incidental, special,
2924 | exemplary, or consequential damages (including, but not limited to,
2925 | procurement of substitute goods or services; loss of use, data, or
2926 | profits; or business interruption) however caused and on any theory of
2927 | liability, whether in contract, strict liability, or tort (including
2928 | negligence or otherwise) arising in any way out of the use of this
2929 | software, even if advised of the possibility of such damage.
2930 |
2931 | */
2932 | ?>
--------------------------------------------------------------------------------
/includes/views/html-licence-data.php:
--------------------------------------------------------------------------------
1 |
45 |
--------------------------------------------------------------------------------
/includes/views/html-variation-licence-data.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | '_variation_licence_activation_limit_' . $loop,
6 | 'name' => '_variation_licence_activation_limit[' . $loop . ']',
7 | 'label' => __( 'Licence activation limit', 'wp-plugin-licencing' ) . ':',
8 | 'placeholder' => __( 'Inherit from parent', 'wp-plugin-licencing' ),
9 | 'value' => get_post_meta( $variation->ID, '_licence_activation_limit', true ),
10 | 'type' => 'number',
11 | 'class' => '',
12 | 'custom_attributes' => array(
13 | 'min' => '',
14 | 'step' => '1'
15 | ) ) ); ?>
16 |
17 |
18 |
19 |
20 | '_variation_licence_expiry_days_' . $loop,
22 | 'name' => '_variation_licence_expiry_days[' . $loop . ']',
23 | 'label' => __( 'Licence expiry days', 'wp-plugin-licencing' ) . ':',
24 | 'placeholder' => __( 'Inherit from parent', 'wp-plugin-licencing' ),
25 | 'value' => get_post_meta( $variation->ID, '_licence_expiry_days', true ),
26 | 'type' => 'number',
27 | 'class' => '',
28 | 'custom_attributes' => array(
29 | 'min' => '',
30 | 'step' => '1'
31 | ) ) ); ?>
32 |
33 |
34 |
--------------------------------------------------------------------------------
/includes/wp-plugin-licencing-functions.php:
--------------------------------------------------------------------------------
1 | post_parent;
12 | } else {
13 | $product_id = $product_or_variation_id;
14 | }
15 | return get_post( $product_id );
16 | }
17 |
18 | /**
19 | * Get the download URL for the package
20 | * @param int $api_product_post_id
21 | * @param string $licence_key
22 | * @param string $activation_email
23 | * @return string
24 | */
25 | function wppl_get_package_download_url( $api_product_post_id, $licence_key, $activation_email ) {
26 | return add_query_arg( array(
27 | 'download_api_product' => $api_product_post_id,
28 | 'licence_key' => $licence_key,
29 | 'activation_email' => $activation_email
30 | ), home_url( '/' ) );
31 | }
32 |
33 | /**
34 | * Get the deactivation url for a licence
35 | * @param string $activation_id of the activation
36 | * @param string $licence_key
37 | * @param string $activation_email
38 | * @return string
39 | */
40 | function wppl_get_licence_deactivation_url( $activation_id, $licence_key, $activation_email ) {
41 | return add_query_arg( array(
42 | 'deactivate_licence' => $activation_id,
43 | 'licence_key' => $licence_key,
44 | 'activation_email' => $activation_email
45 | ) );
46 | }
47 |
48 | /**
49 | * Get the renew url for a licence
50 | * @param string $licence_key
51 | * @param string $activation_email
52 | * @return string
53 | */
54 | function wppl_get_licence_renewal_url( $licence_key, $activation_email ) {
55 | return add_query_arg( array(
56 | 'renew_licence' => $licence_key,
57 | 'activation_email' => $activation_email
58 | ) );
59 | }
60 |
61 | /**
62 | * Get a licence from its key
63 | * @param string $licence_key
64 | * @return object
65 | */
66 | function wppl_get_licence_from_key( $licence_key ) {
67 | global $wpdb;
68 |
69 | return $wpdb->get_row( $wpdb->prepare( "
70 | SELECT * FROM {$wpdb->prefix}wp_plugin_licencing_licences
71 | WHERE licence_key = %s
72 | AND (
73 | date_expires IS NULL
74 | OR date_expires = '0000-00-00 00:00:00'
75 | OR date_expires > NOW()
76 | )
77 | ", $licence_key ) );
78 | }
79 |
80 | /**
81 | * Get a licence from its activation_email
82 | * @param string $activation_email
83 | * @return object
84 | */
85 | function wppl_get_licences_from_activation_email( $activation_email ) {
86 | global $wpdb;
87 |
88 | return $wpdb->get_results( $wpdb->prepare( "
89 | SELECT * FROM {$wpdb->prefix}wp_plugin_licencing_licences
90 | WHERE activation_email = %s
91 | AND (
92 | date_expires IS NULL
93 | OR date_expires = '0000-00-00 00:00:00'
94 | OR date_expires > NOW()
95 | )
96 | ", $activation_email ) );
97 | }
98 |
99 | /**
100 | * Get activations for a given key
101 | * @param string $licence_key
102 | * @param int $api_product_id
103 | * @param int|null $active
104 | * @return object
105 | */
106 | function wppl_get_licence_activations( $licence_key, $api_product_id, $active = null ) {
107 | global $wpdb;
108 |
109 | if ( is_null( $active ) ) {
110 | return $wpdb->get_results( $wpdb->prepare( "
111 | SELECT * FROM {$wpdb->prefix}wp_plugin_licencing_activations
112 | WHERE licence_key = %s AND api_product_id = %s
113 | ", $licence_key, $api_product_id ) );
114 | } else {
115 | return $wpdb->get_results( $wpdb->prepare( "
116 | SELECT * FROM {$wpdb->prefix}wp_plugin_licencing_activations
117 | WHERE licence_key = %s AND api_product_id = %s AND activation_active = %d
118 | ", $licence_key, $api_product_id, $active ) );
119 | }
120 | }
121 |
122 | /**
123 | * Get activations for a given key
124 | * @param string $licence_key
125 | * @param int $api_product_id
126 | * @param int|null $active
127 | * @return object
128 | */
129 | function wppl_is_licence_activated( $licence_key, $api_product_id, $instance ) {
130 | global $wpdb;
131 |
132 | return $wpdb->get_var( $wpdb->prepare( "
133 | SELECT activation_id FROM {$wpdb->prefix}wp_plugin_licencing_activations
134 | WHERE licence_key = %s
135 | AND api_product_id = %s
136 | AND activation_active = 1
137 | AND instance = %s
138 | ", $licence_key, $api_product_id, $instance ) ) ? true : false;
139 | }
140 |
141 | /**
142 | * Get file path of the package to download
143 | * @param int $api_product_post_id
144 | * @return string
145 | */
146 | function wppl_get_package_file_path( $api_product_post_id ) {
147 | return get_post_meta( $api_product_post_id, '_package', true );
148 | }
149 |
150 | /**
151 | * Get API product permissions which a WC product grants
152 | * @param int $product_or_variation_id
153 | * @return array
154 | */
155 | function wppl_get_licence_api_product_permissions( $product_or_variation_id ) {
156 | if ( 'product_variation' === get_post_type( $product_or_variation_id ) ) {
157 | $variation = get_post( $product_or_variation_id );
158 | $product_id = $variation->post_parent;
159 | } else {
160 | $product_id = $product_or_variation_id;
161 | }
162 |
163 | return (array) json_decode( get_post_meta( $product_id, '_api_product_permissions', true ) );
164 | }
165 |
166 | /**
167 | * Covert a post name (api product id) into the actual post ID
168 | * string $api_product_id
169 | * @return int
170 | */
171 | function wppl_get_api_product_post_id( $api_product_id ) {
172 | $api_product = get_page_by_path( $api_product_id, 'object', 'api_product' );
173 | return isset( $api_product->ID ) ? $api_product->ID : 0;
174 | }
175 |
176 | /**
177 | * Check if a logged in user has a package
178 | * @return bool
179 | */
180 | function wppl_user_has_active_licence() {
181 | global $wpdb;
182 |
183 | if ( ! is_user_logged_in() ) {
184 | return;
185 | }
186 |
187 | $current_user = wp_get_current_user();
188 |
189 | return $wpdb->get_row( $wpdb->prepare( "
190 | SELECT 1 FROM {$wpdb->prefix}wp_plugin_licencing_licences
191 | WHERE activation_email = %s OR user_id = %d
192 | AND (
193 | date_expires IS NULL
194 | OR date_expires = '0000-00-00 00:00:00'
195 | OR date_expires > NOW()
196 | )
197 | ", $current_user->user_email, get_current_user_id() ) ) ? true : false;
198 | }
199 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wp-plugin-licencing",
3 | "title": "WP Plugin Licencing",
4 | "version": "1.0.0",
5 | "homepage": "http://wordpress.org/plugins/wp-job-manager/",
6 | "main": "Gruntfile.js",
7 | "devDependencies": {
8 | "grunt": "~0.4.2",
9 | "grunt-contrib-uglify": "~0.3.2",
10 | "grunt-contrib-less": "~0.9.0",
11 | "grunt-contrib-cssmin": "~0.7.0",
12 | "grunt-shell": "~0.6.4",
13 | "grunt-contrib-watch": "~0.5.3",
14 | "grunt-contrib-copy": "~0.5.0",
15 | "grunt-contrib-clean": "0.5.x",
16 | "grunt-wp-deploy": "0.3.x"
17 | },
18 | "engines": {
19 | "node": ">=0.8.0",
20 | "npm": ">=1.1.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/templates/email-keys.php:
--------------------------------------------------------------------------------
1 | 0 ) : ?>
2 |
3 |
4 |
5 | product_id ); ?>
6 | -
7 | post_title ); ?>: licence_key; ?>
8 | product_id ) ) {
10 | echo '
';
11 | foreach ( $api_product_permissions as $api_product_permission ) {
12 | echo '- ' . sprintf( __( 'Download %s', 'wp-plugin-licencing' ), get_the_title( $api_product_permission ) ) . '
';
13 | }
14 | echo '
';
15 | }
16 | ?>
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/templates/lost-licence-email.php:
--------------------------------------------------------------------------------
1 | product_id ) ) {
17 | echo "===============\n";
18 | foreach ( $api_product_permissions as $api_product_permission ) {
19 | echo esc_html( get_the_title( $api_product_permission ) ) . "\n";
20 | echo sprintf( __( 'Key: %s', 'wp-plugin-licencing' ), $key->licence_key ) . "\n";
21 | if ( $key->activation_limit ) {
22 | echo sprintf( __( 'Activation limit: %s', 'wp-plugin-licencing' ), absint( $key->activation_limit ) ) . "\n";
23 | }
24 | if ( $key->date_expires ) {
25 | echo sprintf( __( 'Expiry date: %s', 'wp-plugin-licencing' ), date_i18n( get_option( 'date_format' ), strtotime( $key->date_expires ) ) ) . "\n";
26 | }
27 | echo sprintf( __( 'Download link: %s', 'wp-plugin-licencing' ), wppl_get_package_download_url( $api_product_permission, $key->licence_key, $key->activation_email ) ) . "\n";
28 | echo "===============\n";
29 | }
30 | echo "\n";
31 | }
32 | }
33 |
34 | echo sprintf( __( "You can input these keys from your WordPress dashboard in the plugins section. Find the plugin in the list and enter the key and your activation email. The activation email in your case will be %s.", 'wp-plugin-licencing' ), $activation_email ) . "\n\n";
35 |
36 | echo sprintf( __( 'Once activated, you will be able to update your plugins normally through the dashboard like any other plugin. If you ever want to de-activate a licence you can de-activate the plugin, or do so from your account page on the %s website (if you have an account).', 'wp-plugin-licencing' ), $blogname ) . "\n\n";
37 |
38 | echo __( "Thanks!", 'wp-plugin-licencing' ) . "\n";
39 |
40 | echo "--\n";
41 |
42 | echo apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) );
--------------------------------------------------------------------------------
/templates/lost-licence-form.php:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/templates/my-licences.php:
--------------------------------------------------------------------------------
1 | 0 ) : ?>
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | product_id );
19 | $activations = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}wp_plugin_licencing_activations WHERE activation_active = 1 AND licence_key=%s;", $key->licence_key ) );
20 |
21 | // The expire time stamp, will be false if no expire date is set
22 | $expire_ts = strtotime( $key->date_expires );
23 | ?>
24 |
25 | post_title ); ?>
26 |
27 | licence_key; ?>
28 |
29 | activation_email ); ?>
30 | 0 ) : ?>
31 | date_expires ) ) ); ?>
32 |
33 |
34 |
35 | activation_limit ? sprintf( __( '%d per product', 'wp-plugin-licencing' ), absint( $key->activation_limit ) ) : __( 'Unlimited', 'wp-plugin-licencing' ); ?>
36 | date_expires && false !== $expire_ts && $expire_ts > 0 && $expire_ts < current_time( 'timestamp' ) ) {
38 | echo '' . __( 'Renew licence', 'wp-plugin-licencing' ) . '';
39 | } else {
40 | if ( $api_product_permissions = wppl_get_licence_api_product_permissions( $key->product_id ) ) {
41 | echo '';
42 | foreach ( $api_product_permissions as $api_product_permission ) {
43 | echo '- ' . get_the_title( $api_product_permission ) . ' (v' . get_post_meta( $api_product_permission, '_version', true ) . ')
';
44 | }
45 | echo '
';
46 | }
47 | }
48 | ?>
49 |
50 |
51 |
52 | api_product_id ) ); ?> — instance ); ?>
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/templates/new-licence-email.php:
--------------------------------------------------------------------------------
1 | product_id ) ) {
16 | foreach ( $api_product_permissions as $api_product_permission ) {
17 | echo "\n====================\n";
18 | echo esc_html( get_the_title( $api_product_permission ) ) . ': ' . wppl_get_package_download_url( $api_product_permission, $key->licence_key, $key->activation_email ) . "\n";
19 | echo $key->licence_key . "";
20 | echo "\n====================\n\n";
21 | }
22 | }
23 |
24 | _e( "You can input this licence on the plugins page within your WordPress dashboard.", 'wp-plugin-licencing' );
25 | echo "\n";
26 | echo "\n";
27 |
28 | // Footer
29 | echo '--' . "\n";
30 | echo apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) );
--------------------------------------------------------------------------------
/wp-plugin-licencing.php:
--------------------------------------------------------------------------------
1 | hide_errors();
61 |
62 | $collate = '';
63 |
64 | if ( $wpdb->has_cap( 'collation' ) ) {
65 | if ( ! empty($wpdb->charset ) ) {
66 | $collate .= "DEFAULT CHARACTER SET $wpdb->charset";
67 | }
68 | if ( ! empty($wpdb->collate ) ) {
69 | $collate .= " COLLATE $wpdb->collate";
70 | }
71 | }
72 |
73 | require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
74 |
75 | // Table for storing licence keys for purchases
76 | $sql = "
77 | CREATE TABLE ". $wpdb->prefix . "wp_plugin_licencing_licences (
78 | licence_key varchar(200) NOT NULL,
79 | order_id bigint(20) NOT NULL DEFAULT 0,
80 | user_id bigint(20) NOT NULL DEFAULT 0,
81 | activation_email varchar(200) NOT NULL,
82 | product_id int(20) NOT NULL,
83 | activation_limit int(20) NOT NULL DEFAULT 0,
84 | date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
85 | date_expires datetime NULL,
86 | PRIMARY KEY (licence_key)
87 | ) $collate;
88 | CREATE TABLE ". $wpdb->prefix . "wp_plugin_licencing_activations (
89 | activation_id bigint(20) NOT NULL auto_increment,
90 | licence_key varchar(200) NOT NULL,
91 | api_product_id varchar(200) NOT NULL,
92 | instance varchar(200) NOT NULL,
93 | activation_date datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
94 | activation_active int(1) NOT NULL DEFAULT 1,
95 | PRIMARY KEY (activation_id)
96 | ) $collate;
97 | CREATE TABLE ". $wpdb->prefix . "wp_plugin_licencing_download_log (
98 | log_id bigint(20) NOT NULL auto_increment,
99 | date_downloaded datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
100 | licence_key varchar(200) NOT NULL,
101 | activation_email varchar(200) NOT NULL,
102 | api_product_id varchar(200) NOT NULL,
103 | user_ip_address varchar(200) NOT NULL,
104 | PRIMARY KEY (log_id)
105 | ) $collate;
106 | ";
107 |
108 | dbDelta( $sql );
109 | }
110 |
111 | /**
112 | * Activation
113 | */
114 | public function handle_activation_api_request() {
115 | include_once( 'includes/class-wp-plugin-licencing-activation-api.php' );
116 | new WP_Plugin_Licencing_Activation_API( $_REQUEST );
117 | }
118 |
119 | /**
120 | * Plugin updates
121 | */
122 | public function handle_update_api_request() {
123 | include_once( 'includes/class-wp-plugin-licencing-update-api.php' );
124 | new WP_Plugin_Licencing_Update_API( $_REQUEST );
125 | }
126 | }
127 |
128 | new WP_Plugin_Licencing();
--------------------------------------------------------------------------------