diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a2eaf9e --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*~ +.DS_Store +.svn +.cvs +*.bak +*.swp \ No newline at end of file diff --git a/assets/css/1.php b/assets/css/1.php new file mode 100644 index 0000000..fefd77e --- /dev/null +++ b/assets/css/1.php @@ -0,0 +1,31 @@ + \ No newline at end of file diff --git a/assets/css/2.php b/assets/css/2.php new file mode 100644 index 0000000..77a4fec --- /dev/null +++ b/assets/css/2.php @@ -0,0 +1,67 @@ + \ No newline at end of file diff --git a/assets/css/3.php b/assets/css/3.php new file mode 100644 index 0000000..fb5f418 --- /dev/null +++ b/assets/css/3.php @@ -0,0 +1,45 @@ + \ No newline at end of file diff --git a/assets/css/4.php b/assets/css/4.php new file mode 100644 index 0000000..1a6aa7e --- /dev/null +++ b/assets/css/4.php @@ -0,0 +1,45 @@ + \ No newline at end of file diff --git a/assets/css/5.php b/assets/css/5.php new file mode 100644 index 0000000..a9272a7 --- /dev/null +++ b/assets/css/5.php @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/assets/css/global.php b/assets/css/global.php new file mode 100644 index 0000000..60e7bc2 --- /dev/null +++ b/assets/css/global.php @@ -0,0 +1,75 @@ + \ No newline at end of file diff --git a/assets/css/ssi-wpjm-admin.css b/assets/css/ssi-wpjm-admin.css new file mode 100644 index 0000000..70b6810 --- /dev/null +++ b/assets/css/ssi-wpjm-admin.css @@ -0,0 +1,12 @@ +img.ssi-image { + width: 400px; + height: 210px; + display: block; +} +.wp-core-ui .generate-ssi-image-button, +.wp-core-ui .delete-ssi-image-button { + margin-bottom: 1em; +} +.wp-core-ui .ssi-hidden { + display: none; +} \ No newline at end of file diff --git a/assets/img/social-placeholder.jpg b/assets/img/social-placeholder.jpg new file mode 100644 index 0000000..20cfc01 Binary files /dev/null and b/assets/img/social-placeholder.jpg differ diff --git a/assets/js/ssi-wpjm-admin.js b/assets/js/ssi-wpjm-admin.js new file mode 100644 index 0000000..835314e --- /dev/null +++ b/assets/js/ssi-wpjm-admin.js @@ -0,0 +1,115 @@ +( function( $ ) { + + // hide the deploy spinner. + $( '.ssi-wpjm-spinner' ).hide(); + + // when the deploy button is clicked. + $( document ).on( 'click', '.generate-ssi-image-button', function(e) { + + // prevent the button taking its normal action. + e.preventDefault(); + + // show the deploy spinner. + $( '.ssi-wpjm-spinner' ).show(); + + // get the deploy endpoint url. + var generateEndpointUrl = $( this ).data( 'endpoint-url' ); + + // do the ajax request for job search. + $.ajax({ + + type: 'get', + url: generateEndpointUrl, + + // what happens on success. + success: function( response, status, request ) { + + // set the image src to the response. + $( '.ssi-image' ).attr( 'src', response.url ); + + // change the media id in the delete button. + $( '.delete-ssi-image-button' ).data( 'media-id', response.id ); + + // remove the hidden class for the delete button. + $( '.delete-ssi-image-button' ).removeClass( 'ssi-hidden' ); + + // add the hidden class for the add button. + $( '.generate-ssi-image-button' ).addClass( 'ssi-hidden' ); + + // hide the deploy spinner. + $( '.ssi-wpjm-spinner' ).hide(); + + }, + + /* what happens on success */ + error: function( response ) { + + console.log( 'Error' ); + console.log( response ); + + } + + }); + + }); + + // when the deploy button is clicked. + $( document ).on( 'click', '.delete-ssi-image-button', function(e) { + + // prevent the button taking its normal action. + e.preventDefault(); + + // show the deploy spinner. + $( '.ssi-wpjm-spinner' ).show(); + + // get the deploy endpoint url. + var deleteEndpoint = $( this ).data( 'endpoint-url' ); + + // get the attachment to delete. + var deleteMediaId = $( this ).data( 'media-id' ); + + // get the placeholder image url. + var placeholderImag = $( this ).data( 'placeholder-img' ); + + // do the ajax request for job search. + $.ajax({ + + url: deleteEndpoint + deleteMediaId + '/?force=true', + method: 'DELETE', + beforeSend: function ( xhr ) { + xhr.setRequestHeader( 'X-WP-Nonce', wpApiSettings.nonce ); + }, + + // what happens on success. + success: function( response, status, request ) { + + // set the image src to the response. + $( '.ssi-image' ).attr( 'src', placeholderImag ); + + // set the media id data. + $( '.ssi-image' ).data( 'media-id', deleteMediaId ); + + // remove the hidden class for the delete button. + $( '.delete-ssi-image-button' ).addClass( 'ssi-hidden' ); + + // remove the hidden class for the generate button. + $( '.generate-ssi-image-button' ).removeClass( 'ssi-hidden' ); + + // hide the deploy spinner. + $( '.ssi-wpjm-spinner' ).hide(); + + }, + + /* what happens on success */ + error: function( response ) { + + console.log( 'Error' ); + console.log( response ); + + } + + }); + + }); + +} )( jQuery ); \ No newline at end of file diff --git a/endpoints/generate.php b/endpoints/generate.php new file mode 100644 index 0000000..82b6173 --- /dev/null +++ b/endpoints/generate.php @@ -0,0 +1,173 @@ + get_option( 'ssi_wpjm_template' ), + 'job_post' => $job_post, + 'bg_color' => get_option( 'ssi_wpjm_bg_color' ), + 'bg_text_color' => get_option( 'ssi_wpjm_text_bg_color' ), + 'text_color' => get_option( 'ssi_wpjm_text_color' ), + 'image' => wp_get_attachment_image_url( ssi_wpjm_get_random_image_id(), 'full' ), + 'logo' => wp_get_attachment_image_url( get_option( 'ssi_wpjm_logo_id' ), 'full' ), +); + +// allow the args to be filtered. +$args = apply_filters( 'ssi_wpjm_endpoint_generate_args', $args ); + +// set the location of the template file, making it filterable. +$template_location = apply_filters( 'ssi_wpjm_generate_template_location', SSI_WPJM_LOCATION . '/templates/', $args ); +$template_css_location = apply_filters( 'ssi_wpjm_generate_template_css_location', SSI_WPJM_LOCATION . '/assets/css/', $args ); + +// start output buffering. +ob_start(); + +// load the template markup, passing our args. +load_template( SSI_WPJM_LOCATION . '/assets/css/global.php', true, $args ); + +// if our template exists. +if ( file_exists( $template_css_location . $args['template'] . '.php' ) ) { + + // load the template markup, passing our args. + load_template( $template_css_location . $args['template'] . '.php', true, $args ); + +} + +// if our template exists. +if ( file_exists( $template_location . $args['template'] . '.php' ) ) { + + // load the template markup, passing our args. + load_template( $template_location . $args['template'] . '.php', true, $args ); + +} + +// get the contents of the buffer, the template markup and clean the buffer. +$text = ob_get_clean(); + +// find all of the strings that need replacing. +preg_match_all( "/\[[^\]]*\]/", $text, $matches ); + +// if we have matches. +if ( $matches !== false ) { + + // loop through the matches. + foreach ( $matches[0] as $match ) { + + // remove the brackets for the string. + $match_value = str_replace( '[', '', $match ); + $match_value = str_replace( ']', '', $match_value ); + + // if this is a meta replace. + if ( strpos( $match_value, 'meta' ) !== false ) { + + // remove the meta: string + $match_key = str_replace( 'meta:', '', $match_value ); + + // get the value of this meta. + $match_value = get_post_meta( $args['job_post'], $match_key, true ); + + // filter the match post value. + $match_value = apply_filters( 'ssi_wpjm_template_' . $match_key, $match_value, $match_key ); + + // replace the original text string with the new + $text = str_replace( $match, $match_value, $text ); + + } + + // if this is a tax replace. + if ( strpos( $match_value, 'tax' ) !== false ) { + + // remove the tax: string + $match_key = str_replace( 'tax:', '', $match_value ); + + // get the value of this meta. + $match_value = wp_strip_all_tags( + get_the_term_list( + $args['job_post'], + $match_key, + '', + ', ', + '' + ) + ); + + // filter the match post value. + $match_value = apply_filters( 'ssi_wpjm_template_' . $match_key, $match_value, $match_key ); + + // replace the original text string with the new + $text = str_replace( $match, $match_value, $text ); + + } + + // if this is a post field replace. + if ( strpos( $match_value, 'post' ) !== false ) { + + // remove the tax: string + $match_key = str_replace( 'post:', '', $match_value ); + + // get the value of this meta. + $match_value = get_post_field( $match_key, $args['job_post'] ); + + // if we have no job post id. + if ( empty( $args['job_post'] ) ) { + + // set the match value to the site title. + $match_value = get_bloginfo( 'title' ); + + } + + // filter the match post value. + $match_value = apply_filters( 'ssi_wpjm_template_' . $match_key, $match_value, $match_key ); + + // replace the original text string with the new + $text = str_replace( $match, $match_value, $text ); + + } + + // if we have a logo. + if ( empty( $args['logo'] ) ) { + + // set the logo to a transparent image. + $args['logo'] = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; + + } + + // if we have a image. + if ( empty( $args['image'] ) ) { + + // set the image to a transparent image. + $args['logo'] = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; + + } + + // if we have a template. + if ( empty( $args['template'] ) ) { + + // default the template. + $args['template'] = '1'; + + } + + // replace the logo string. + $text = str_replace( '[logo]', $args['logo'], $text ); + + // replace the template string. + $text = str_replace( '[template]', $args['template'], $text ); + + // replace the image string. + $text = str_replace( '[image]', $args['image'], $text ); + + } + +} + +echo $text; diff --git a/inc/endpoints.php b/inc/endpoints.php new file mode 100644 index 0000000..8caf49d --- /dev/null +++ b/inc/endpoints.php @@ -0,0 +1,226 @@ + 'GET', + 'callback' => 'ssi_wpjm_generate_endpoint_output', + 'permission_callback' => '__return_true', + ) + ); + +} + +add_action( 'rest_api_init', 'ssi_wpjm_register_rest_endpoint' ); + +/** + * Callback function used for the registered rest route for getting latest post content. + * + * @param \WP_REST_Request $request The paramters passed to the endpoint url. + * @return mixed THe HTML outputs for the requested slots posts. + */ +function ssi_wpjm_generate_endpoint_output( \WP_REST_Request $request ) { + + // if no post ID is present. + if ( empty( $request['post_id'] ) ) { + + // output error. + return array( + 'success' => false, + 'error' => __( 'No post ID provided.', 'simple-social-image-wpjm' ), + ); + + } + + // get the license key. + $license_key = get_option( 'ssi_wpjm_license_key' ); + + // if we have no license key. + if ( empty( $license_key ) ) { + + // output error. + return array( + 'success' => false, + 'error' => __( 'No license key provided.', 'simple-social-image-wpjm' ), + ); + + } + + $social_image_html_url = home_url( '/ssi-wpjm/v1/generate-html/' ); + $social_image_html_url = add_query_arg( + array( + 'job_id' => absint( $request['post_id'] ), + 'timestamp' => time(), + ), + $social_image_html_url + ); + + // set the URL of the simple social images api. + $api_url = ssi_wpjm_generate_api_url(); + + // add the paramters to the api_url. + $api_url = add_query_arg( + apply_filters( + 'ssi_wpjm_api_url_query_args', + array( + 'license_key' => sanitize_text_field( $license_key ), + 'site_url' => home_url(), + 'url' => urlencode( $social_image_html_url ), + 'element' => '.hdsmi-template', + 'ttl' => 300, + ), + ), + $api_url + ); + + // send the request to the api. + $response = wp_remote_get( + $api_url, + array( + 'sslverify' => false, + ) + ); + + // if there was an error. + if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) { + + // output error. + return array( + 'success' => false, + 'error' => __( 'Request returned an error.', 'simple-social-image-wpjm' ), + ); + + } + + // get the body of the request, decoded as an array. + $response = json_decode( wp_remote_retrieve_body( $response ), true ); + + // if we have no url returned. + if ( empty( $response['url'] ) ) { + + // output error. + return array( + 'success' => false, + 'error' => __( 'No image was generated.', 'simple-social-image-wpjm' ), + ); + + } + + // we are outside of WP Admin so need to include these files. + require_once( ABSPATH . 'wp-admin/includes/media.php' ); + require_once( ABSPATH . 'wp-admin/includes/file.php' ); + require_once( ABSPATH . 'wp-admin/includes/image.php' ); + + // grab the image and store in the media library. + $image_id = media_sideload_image( $response['url'], absint( $request['post_id'] ), '', 'id' ); + + // if we have an image set. + if ( ! is_wp_error( $image_id ) ) { + + // save meta data indicating this is image generated by hd og images. + update_post_meta( $image_id, 'ssi_wpjm_image', true ); + + // get the current image id for the og:image. + $current_image_id = get_post_meta( absint( $request['post_id'] ), 'ssi_wpjm_image_id', true ); + + // if we have a current image. + if ( ! empty( $current_image_id ) ) { + + // delete the attachment. + wp_delete_attachment( $current_image_id ); + + } + + // store the image ID as meta against the job. + update_post_meta( absint( $request['post_id'] ), 'ssi_wpjm_image_id', $image_id ); + + } + + return apply_filters( + 'ssi_wpjm_generated_social_image', + array( + 'id' => $image_id, + 'url' => wp_get_attachment_image_url( $image_id, 'ssi_image' ), + ), + $request + ); + +} diff --git a/inc/loader.php b/inc/loader.php new file mode 100644 index 0000000..daeb735 --- /dev/null +++ b/inc/loader.php @@ -0,0 +1,20 @@ +ID ); + + // get the URL for the current social image. + $social_image_url = wp_get_attachment_image_url( $social_image_id, 'ssi_image' ); + + // if we don't have a social image URL. + if ( empty ( $social_image_url ) ) { + + // set the url of the image to the placeholder. + $social_image_url = SSI_WPJM_LOCATION_URL . '/assets/img/social-placeholder.jpg'; + + } + + ?> +
+ + + + + + + + + + + + + + + + 'ssi_wpjm_license_key', + 'label' => __( 'License Key', 'simple-social-images-wpjm' ), + 'desc' => __( 'Enter your license key for the Simple Social Images service. You can signup for a license here.', 'simple-social-images-wpjm' ), + 'type' => 'text', + 'attributes' => array(), + ), + array( + 'name' => 'ssi_wpjm_template', + 'std' => '', + 'label' => __( 'Template', 'simple-social-images-wpjm' ), + 'desc' => __( 'Choose your template for the social sharing images that are generated.', 'simple-social-images-wpjm' ), + 'type' => 'select', + 'options' => array( + '1' => __( 'Template 1', 'simple-social-images-wpjm' ), + '2' => __( 'Template 2', 'simple-social-images-wpjm' ), + '3' => __( 'Template 3', 'simple-social-images-wpjm' ), + '4' => __( 'Template 4', 'simple-social-images-wpjm' ), + '5' => __( 'Template 5', 'simple-social-images-wpjm' ), + ), + 'attributes' => array(), + ), + array( + 'name' => 'ssi_wpjm_text_color', + 'label' => __( 'Text Colour', 'simple-social-images-wpjm' ), + 'desc' => __( 'Enter the hex code of your text colour. The text on your image will be this colour.', 'simple-social-images-wpjm' ), + 'type' => 'text', + 'attributes' => array(), + ), + array( + 'name' => 'ssi_wpjm_text_bg_color', + 'label' => __( 'Text Background Colour', 'simple-social-images-wpjm' ), + 'desc' => __( 'Enter the hex code of your text background colour. The colour behind your text on your image will be this colour.', 'simple-social-images-wpjm' ), + 'type' => 'text', + 'attributes' => array(), + ), + array( + 'name' => 'ssi_wpjm_bg_color', + 'label' => __( 'Background Colour', 'simple-social-images-wpjm' ), + 'desc' => __( 'Enter the hex code of your background colour.', 'simple-social-images-wpjm' ), + 'type' => 'text', + 'attributes' => array(), + ), + array( + 'name' => 'ssi_wpjm_images', + 'label' => __( 'Image IDs', 'simple-social-images-wpjm' ), + 'desc' => __( 'Enter a comma seperated list of WordPress attachment IDs of the images you want to be randomly used when your image is generated.', 'simple-social-images-wpjm' ), + 'type' => 'text', + 'attributes' => array(), + ), + array( + 'name' => 'ssi_wpjm_logo_id', + 'label' => __( 'Logo Attachment ID', 'simple-social-images-wpjm' ), + 'desc' => __( 'Enter the attachment IDs of the image you want to use as the logo.', 'simple-social-images-wpjm' ), + 'type' => 'text', + 'attributes' => array(), + ), + ), + array( + 'before' => __( 'Simple Social Images generates a automatic, beautiful and branded image for each job which, if the job is shared on social media, for example LinkedIn, shows as the preview image for the job. This really makes your jobs stand out from the crowd in social media feeds. Enter your settings below.', 'simple-social-image-wpjm' ), + ), + ); + + // return the settings array. + return $settings; + +} + +add_filter( 'job_manager_settings', 'ssi_wpjm_register_settings', 10, 1 ); diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..6b50448 --- /dev/null +++ b/readme.txt @@ -0,0 +1,57 @@ +=== Simple Social Images for WP Job Manager === +Contributors: wpmarkuk, keithdevon, highrisedigital +Tags: social sharing, jobs, open graph +Requires at least: 6.0 +Tested up to: 6.0 +Stable tag: 1.0 +Requires PHP: 7.0 +License: GPLv2 or later +License URI: https://www.gnu.org/licenses/gpl-2.0.html + +This plugin when combined with a Simple Social Images license, automatically generates beautiful and branded images which are displayed in social media feeds when a job, written in WP Job Manager is shared on social media platforms. + +== Description == + +This is the long description. No limit, and you can use Markdown (as well as in the following sections). + +For backwards compatibility, if this section is missing, the full length of the short description will be used, and +Markdown parsed. + +A few notes about the sections above: + +* "Contributors" is a comma separated list of wordpress.org usernames +* "Tags" is a comma separated list of tags that apply to the plugin +* "Requires at least" is the lowest version that the plugin will work on +* "Tested up to" is the highest version that you've *successfully used to test the plugin* +* Stable tag must indicate the Subversion "tag" of the latest stable version + +Note that the `readme.txt` value of stable tag is the one that is the defining one for the plugin. If the `/trunk/readme.txt` file says that the stable tag is `4.3`, then it is `/tags/4.3/readme.txt` that'll be used for displaying information about the plugin. + +If you develop in trunk, you can update the trunk `readme.txt` to reflect changes in your in-development version, without having that information incorrectly disclosed about the current stable version that lacks those changes -- as long as the trunk's `readme.txt` points to the correct stable tag. + +If no stable tag is provided, your users may not get the correct version of your code. + +== Frequently Asked Questions == + += Does this plugin require a paid license? = + +Yes, the plugin requires a paid license for [Simple Social Images](https://simplesocialimages.com). This gives access to the API to generate the images for each job. + += Will all the generated images not look the same? = + +No, you can select which template you want to use, and then each image that is generated can have a random background image (which you can also provide) and will also include the job title, salary and location. + +== Screenshots == + +1. This screen shot description corresponds to screenshot-1.(png|jpg|jpeg|gif). Screenshots are stored in the /assets directory. +2. This is the second screen shot + +== Changelog == + += 1.0 = +* A change since the previous version. +* Another change. + +== Upgrade Notice == + +Updates provided via WordPress.org. \ No newline at end of file diff --git a/simple-social-images-wpjm.php b/simple-social-images-wpjm.php new file mode 100644 index 0000000..49ef0d4 --- /dev/null +++ b/simple-social-images-wpjm.php @@ -0,0 +1,156 @@ +Simple Social Images. + * Version: 1.0 + * Author: Highrise Digital + * Author URI: https://highrise.digital/ + * Text Domain: simple-social-images-wpjm + * Domain Path: /languages/ + * License: GPL2+ + */ + +// define variable for path to this plugin file. +define( 'SSI_WPJM_LOCATION', dirname( __FILE__ ) ); +define( 'SSI_WPJM_LOCATION_URL', plugins_url( '', __FILE__ ) ); +define( 'SSI_WPJM_VERSION', '1.0' ); + +/** + * Function to run on plugins load. + */ +function ssi_wpjm_plugins_loaded() { + + $locale = apply_filters( 'plugin_locale', get_locale(), 'simple-social-images-wpjm' ); + load_textdomain( 'simple-social-images-wpjm', WP_LANG_DIR . '/simple-social-images-wpjm/simple-social-images-wpjm-' . $locale . '.mo' ); + load_plugin_textdomain( 'simple-social-images-wpjm', false, plugin_basename( dirname( __FILE__ ) ) . '/languages/' ); + +} + +add_action( 'plugins_loaded', 'ssi_wpjm_plugins_loaded' ); + +// load in the loader file which loads everything up. +require_once( dirname( __FILE__ ) . '/inc/loader.php' ); + +/** + * Grabs a random image ID from those added to the settings page. + */ +function ssi_wpjm_get_random_image_id() { + + // get the image ids from options. + $image_ids = get_option( 'ssi_wpjm_images' ); + + // convert the id string into an array. + $images = explode( ',', $image_ids ); + + $image_id_key = array_rand( $images, 1 ); + $image_id = $images[ $image_id_key ]; + + return absint( $image_id ); + +} + +/** + * Returns the URL of the SSI API to generate an image. + */ +function ssi_wpjm_generate_api_url() { + + return apply_filters( + 'ssi_wpjm_generate_api_url', + 'https://simplesocialimages.com/ssi-api/v1/generate/' + ); + +} + +/** + * Enqueues the admin js file. + * Only enqueued on the post edit screen for WPJM jobs. + */ +function ssi_wpjm_enqueue_scripts( $hook ) { + + // if this is not the post edit screen. + if ( $hook !== 'post.php' ) { + return; + } + + global $post_type; + + // if the post type is not job listing. + if ( $post_type !== 'job_listing' ) { + return; + } + + wp_localize_script( + 'wp-api', + 'wpApiSettings', + array( + 'root' => esc_url_raw( rest_url() ), + 'nonce' => wp_create_nonce( 'wp_rest' ) + ) + ); + + // register the js. + wp_enqueue_script( + 'ssi_wpjm_admin_js', + SSI_WPJM_LOCATION_URL . '/assets/js/ssi-wpjm-admin.js', + array( 'jquery' ), + SSI_WPJM_VERSION, + true + ); + + // register the admin css. + wp_enqueue_style( + 'ssi_wpjm_admin_css', + SSI_WPJM_LOCATION_URL . '/assets/css/ssi-wpjm-admin.css', + array(), + SSI_WPJM_VERSION + ); + +} + +add_action( 'admin_enqueue_scripts', 'ssi_wpjm_enqueue_scripts' ); + +/** + * Add the og:image size in WP. + * Allows images to be cropped to og:image size. + */ +function ssi_wpjm_add_image_size() { + + // add the og:image size. + add_image_size( 'ssi_image', 1200, 630, true ); + +} + +add_action( 'after_setup_theme', 'ssi_wpjm_add_image_size' ); + +function ssi_wpjm_has_image( $post_id = 0 ) { + + // if we have no post id to check. + if ( $post_id === 0 ) { + + // use current global post id. + global $post; + $post_id = $post->ID; + + } + + // get the image id stored as meta. + $image_id = get_post_meta( $post_id, '', true ); + + // if we have no image id. + if ( empty( $image_id ) ) { + return 0; + } + + // get the image url for the associated meta. + $image_url = wp_get_attachment_image_url( $image_id, 'ssi_image' ); + + // if we have no image url. + if ( $image_url === false ) { + return 0; + } + + // go this far, we must have an image. + return apply_filters( 'ssi_wpjm_has_image', $image_id, $post_id ); + +} diff --git a/templates/1.php b/templates/1.php new file mode 100644 index 0000000..bd24861 --- /dev/null +++ b/templates/1.php @@ -0,0 +1,11 @@ +