diff --git a/.forgejo/workflows/package.yml b/.forgejo/workflows/package.yml new file mode 100644 index 0000000..f6325b8 --- /dev/null +++ b/.forgejo/workflows/package.yml @@ -0,0 +1,94 @@ +# Building and releasing Wordpress plugins is pretty simple +# It's just zipping up some PHP. This doesn't contain any unit tests +# this is just zipping and shipping. +# +# Generally speaking your directory structure would be: +# . +# ├── README.md +# └── src +# └── wordpress-plugin.php +# +# With the repository name being the name of your plugin to make it easier +# to Google for. Up to you though; you can configure the source dir below. +# +# When you tag a commit and the tag stats with 'v', this workflow will +# zip and ship the Wordpress plugin to the 'releases' tab of Github or Forgejo + +name: Build & Release WordPress Plugin + +on: + push: + tags: + - 'v*' + +jobs: + build: + runs-on: docker + container: debian:12 + + env: + SOURCE_DIR: "src" + + + steps: + + - name: Prepare Build Environment + run: | + apt update && apt -y install zip unzip nodejs curl jq + + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Package plugin + run: | + # Set plugin name like this because it's easier than inline + PLUGIN_NAME=$(echo $GITHUB_REPOSITORY | awk -F'/' '{ print $2 }') + + # Create a build directory to ensure that we're correctly creating + # a single child directory in the zip. + mkdir -p build + cp -r ${SOURCE_DIR} "build/${PLUGIN_NAME}" + + # Zip it up for shipping + cd build + zip -r "../${PLUGIN_NAME}.zip" "${PLUGIN_NAME}" + cd - + unzip -l "${PLUGIN_NAME}.zip" + + - name: Create Release if not exists + run: | + # Curl request that uses the repos token & Forgejo's built-in variables + # to create a new release based on the tag that you have pushed + # + # It doesn't care if the release exists or not. + curl -X POST "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases" \ + -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + -H "Content-Type: application/json" \ + -d '{ + "tag_name": "'"${GITHUB_REF##*/}"'", + "target_commitish": "main", + "name": "'"${GITHUB_REF##*/}"'", + "body": "Auto release", + "draft": false, + "prerelease": false + }' || true + + - name: Upload Plugin Zip to Release + run: | + # Set some variables because it's cleaner than in-line + TAG_NAME="${GITHUB_REF##*/}" + REPO_NAME="$(basename $GITHUB_REPOSITORY)" + PLUGIN_NAME=$(echo $GITHUB_REPOSITORY | awk -F'/' '{ print $2 }') + + # Get the release ID created in the last step + RELEASE_ID=$(curl -s \ + -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG_NAME}" \ + | jq -r '.id') + + # Pushes he zip file to be shown in the "Releases" tab under this release. + curl -X POST "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets" \ + -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + -F "name=${PLUGIN_NAME}.zip" \ + -F "attachment=@${PLUGIN_NAME}.zip" + diff --git a/README.md b/README.md index 7fe8746..8c28be6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,42 @@ -# fwp-recaptcha +# fwp-calendar -Provides basic Google Recaptcha protections for your Wordpress blog. \ No newline at end of file +Originally created for a customer, I imagine that this would help some people +wanting to avoid Wordpresses increasing "pay to use" direction. + +Accepts a list of iCal URLs (without auth) and publishes to a page. + +Almost entirely generated with ChatGPT. I lay no claim that I have manually +written this entire thing myself. I just need something working and fast. + +## MVP + +The MVP has very basic functionality. We leverage two external libraries however +have included them within the plugin: + +1. [FullCalendar](https://github.com/fullcalendar/fullcalendar) +2. [ics-parser](https://github.com/u01jmg3/ics-parser) + +We pull from a public iCal/ICS URL via the server. We then process and display +this information when a shortcode (`[event-calendar]`) is called. + +We store a list/array of URLs to pull from, so you should be able to add a +couple. + +## Usage + +Just install then look in "Settings" for the "FWP Calendar" section. + +You can add a simple list of ICS/iCal URLs there however they must be publicly +accessible. I tested and it works with Google calendars. + +I created this for a customer who had a list of upcoming events displayed on +their website. They were manually updating the events calendar entirely through +Wordpress, which seemed clunky. + +This plugin allows you to maintain a community events calendar from within your +Orgs typical public calendar. Visitors can see events spread out on a calendar +and upcoming events below. + +This is good because you can provide the calendar for people to add to their +phones or import into their own calendars. Your options are endless when you +don't use shitty proprietary standards! diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..ffa0e54 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,34 @@ +services: + db: + image: mysql:8.0 + container_name: wp_test_db + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: rootpassword + MYSQL_DATABASE: wordpress + MYSQL_USER: wordpress + MYSQL_PASSWORD: wordpress + volumes: + - db_data:/var/lib/mysql + + wordpress: + image: wordpress:latest + container_name: wp_test_app + depends_on: + - db + ports: + - "8080:80" + restart: unless-stopped + environment: + WORDPRESS_DB_HOST: db:3306 + WORDPRESS_DB_USER: wordpress + WORDPRESS_DB_PASSWORD: wordpress + WORDPRESS_DB_NAME: wordpress + WORDPRESS_DEBUG: 'true' + WORDPRESS_DEBUG_LOG: 'true' + WORDPRESS_DEBUG_DISPLAY: 'false' + volumes: + - ./src:/var/www/html/wp-content/plugins/fwp-recaptcha +volumes: + db_data: + diff --git a/src/funcs/recaptcha.php b/src/funcs/recaptcha.php new file mode 100644 index 0000000..df19a60 --- /dev/null +++ b/src/funcs/recaptcha.php @@ -0,0 +1,96 @@ + [ + 'secret' => $secret_key, + 'response' => $token, + 'remoteip' => $_SERVER['REMOTE_ADDR'] ?? '', + ], + ]); + + if (is_wp_error($response)) return false; + + $data = json_decode(wp_remote_retrieve_body($response), true); + return !empty($data['success']) && $data['score'] >= 0.5; +} + +add_filter('authenticate', 'fwp_verify_recaptcha_on_login', 30, 3); +function fwp_verify_recaptcha_on_login($user, $username, $password) { + $enabled_forms = get_option('fwp_recaptcha_enabled_forms', []); + if (!in_array('login', $enabled_forms)) return $user; + + if (!isset($_POST['g-recaptcha-response']) || !fwp_verify_recaptcha_token($_POST['g-recaptcha-response'])) { + return new WP_Error('recaptcha_failed', __('reCAPTCHA verification failed.')); + } + + return $user; +} + +add_filter('registration_errors', 'fwp_verify_recaptcha_on_register', 10, 3); +function fwp_verify_recaptcha_on_register($errors, $sanitized_user_login, $user_email) { + $enabled_forms = get_option('fwp_recaptcha_enabled_forms', []); + if (!in_array('register', $enabled_forms)) return $errors; + + if (!isset($_POST['g-recaptcha-response']) || !fwp_verify_recaptcha_token($_POST['g-recaptcha-response'])) { + $errors->add('recaptcha_failed', __('reCAPTCHA verification failed.')); + } + + return $errors; +} + +add_action('pre_comment_on_post', 'fwp_verify_recaptcha_on_comment'); +function fwp_verify_recaptcha_on_comment($comment_post_ID) { + $enabled_forms = get_option('fwp_recaptcha_enabled_forms', []); + if (!in_array('comment', $enabled_forms)) return; + + if (!isset($_POST['g-recaptcha-response']) || !fwp_verify_recaptcha_token($_POST['g-recaptcha-response'])) { + wp_die(__('reCAPTCHA verification failed.'), __('Comment Blocked'), [ + 'back_link' => true + ]); + } +} + diff --git a/src/funcs/settings.php b/src/funcs/settings.php new file mode 100644 index 0000000..d14ff68 --- /dev/null +++ b/src/funcs/settings.php @@ -0,0 +1,77 @@ + +
+

FWP reCAPTCHA Settings

+

+ You can find more information about Google Recaptcha v3 in Googles Documentation. You can easily register new keys using the Recaptcha Admin Panels creation page or manage your keys on Googles Recaptcha management portal. +

+
+ + + + + + + + + + + + + + + + +
+
+ '; +} + +// Validate login form +add_filter('authenticate', 'urc_verify_recaptcha_on_login', 30, 3); +function urc_verify_recaptcha_on_login($user, $username, $password) { + if (!urc_is_recaptcha_valid()) { + return new WP_Error('recaptcha_invalid', __('ERROR: Please complete the reCAPTCHA.')); + } + return $user; +} + +// Validate registration +add_action('registration_errors', 'urc_verify_recaptcha_on_register', 10, 3); +function urc_verify_recaptcha_on_register($errors, $sanitized_user_login, $user_email) { + if (!urc_is_recaptcha_valid()) { + $errors->add('recaptcha_invalid', __('ERROR: Please complete the reCAPTCHA.')); + } + return $errors; +} + +// Validate comment form +add_filter('preprocess_comment', 'urc_verify_recaptcha_on_comment'); +function urc_verify_recaptcha_on_comment($commentdata) { + if (!urc_is_recaptcha_valid()) { + wp_die(__('ERROR: Please complete the reCAPTCHA.')); + } + return $commentdata; +} + +// reCAPTCHA validation helper +function urc_is_recaptcha_valid() { + if (isset($_POST['g-recaptcha-response'])) { + $response = wp_remote_post('https://www.google.com/recaptcha/api/siteverify', [ + 'body' => [ + 'secret' => URC_SECRET_KEY, + 'response' => sanitize_text_field($_POST['g-recaptcha-response']), + 'remoteip' => $_SERVER['REMOTE_ADDR'], + ] + ]); + + if (!is_wp_error($response)) { + $data = json_decode(wp_remote_retrieve_body($response), true); + return isset($data['success']) && $data['success'] === true; + } + } + return false; +} + diff --git a/src/fwp-recaptcha.php b/src/fwp-recaptcha.php new file mode 100644 index 0000000..4fb4300 --- /dev/null +++ b/src/fwp-recaptcha.php @@ -0,0 +1,15 @@ +