__( 'Clickup', 'advanced-form-integration' ), 'tasks' => array( 'create_task' => __( 'Create Task', 'advanced-form-integration' ) ) ); return $actions; } add_filter( 'adfoin_settings_tabs', 'adfoin_clickup_settings_tab', 10, 1 ); function adfoin_clickup_settings_tab( $providers ) { $providers['clickup'] = __( 'Clickup', 'advanced-form-integration' ); return $providers; } add_action( 'adfoin_settings_view', 'adfoin_clickup_settings_view', 10, 1 ); function adfoin_clickup_settings_view( $current_tab ) { if( $current_tab != 'clickup' ) { return; } if ( ! class_exists( 'ADFOIN_Account_Manager' ) ) { require_once plugin_dir_path( __FILE__ ) . '../../includes/class-adfoin-account-manager.php'; } $fields = array( array( 'name' => 'apiToken', 'label' => __( 'API Token', 'advanced-form-integration' ), 'type' => 'text', 'required' => true, 'mask' => true, 'placeholder' => __( 'Enter your API Token', 'advanced-form-integration' ), 'show_in_table' => true ) ); $instructions = sprintf( '

%s

', __('Go to My Settings > Apps. Generate API Token and copy it.', 'advanced-form-integration') ); ADFOIN_Account_Manager::render_settings_view( 'clickup', __( 'ClickUp', 'advanced-form-integration' ), $fields, $instructions ); } add_action( 'wp_ajax_adfoin_get_clickup_credentials', 'adfoin_get_clickup_credentials', 10, 0 ); /* * Get ClickUp credentials */ function adfoin_get_clickup_credentials() { if ( ! class_exists( 'ADFOIN_Account_Manager' ) ) { require_once plugin_dir_path( __FILE__ ) . '../../includes/class-adfoin-account-manager.php'; } ADFOIN_Account_Manager::ajax_get_credentials_list( 'clickup' ); } add_action( 'wp_ajax_adfoin_save_clickup_credentials', 'adfoin_save_clickup_credentials', 10, 0 ); /* * Save ClickUp credentials */ function adfoin_save_clickup_credentials() { if ( ! class_exists( 'ADFOIN_Account_Manager' ) ) { require_once plugin_dir_path( __FILE__ ) . '../../includes/class-adfoin-account-manager.php'; } ADFOIN_Account_Manager::ajax_save_credentials( 'clickup', array( 'apiToken' ) ); } /* * ClickUp Credentials List */ function adfoin_clickup_get_credentials_list() { $credentials = adfoin_read_credentials( 'clickup' ); foreach ($credentials as $option) { printf('', esc_attr($option['id']), esc_html($option['title'])); } } // Legacy single-account import: surfaces old `adfoin_clickup_*` options // as a Legacy Account record when the new credentials store is empty. add_action( 'plugins_loaded', function() { if ( class_exists( 'ADFOIN_Account_Manager' ) ) { ADFOIN_Account_Manager::register_legacy_option_importer( 'clickup', array( 'apiToken' => 'adfoin_clickup_api_token', ) ); } }, 20 ); // Deprecated - kept for backward compatibility add_action( 'admin_post_adfoin_save_clickup_api_token', 'adfoin_save_clickup_api_token', 10, 0 ); function adfoin_save_clickup_api_token() { // Security Check // Authorization check adfoin_require_manage_options(); if (! wp_verify_nonce( $_POST['_nonce'], 'adfoin_clickup_settings' ) ) { die( __( 'Security check Failed', 'advanced-form-integration' ) ); } $api_token = sanitize_text_field( wp_unslash( $_POST["adfoin_clickup_api_token"] ) ); // Save tokens update_option( "adfoin_clickup_api_token", $api_token ); advanced_form_integration_redirect( "admin.php?page=advanced-form-integration-settings&tab=clickup" ); } add_action( 'adfoin_action_fields', 'adfoin_clickup_action_fields' ); function adfoin_clickup_action_fields() { ?> ClickUp.', 'advanced-form-integration' ) ); if ( $record ) { adfoin_add_to_log( $error, 'https://api.clickup.com/api/v2/' . $endpoint, array( 'method' => $method, 'body' => $data ), $record ); } return $error; } $base_url = 'https://api.clickup.com/api/v2/'; $url = $base_url . $endpoint; $args = array( 'timeout' => 30, 'method' => $method, 'headers' => array( 'Content-Type' => 'application/json', 'Authorization' => $api_token, ), ); if ('POST' == $method || 'PUT' == $method) { $args['body'] = wp_json_encode($data); } $response = wp_remote_request($url, $args); // ClickUp returns HTTP 429 with X-RateLimit-Reset (Unix timestamp) when // the per-token rate limit is exceeded. Retry once after waiting for the // reset (clamped to <=30s). Falls back to Retry-After if present. if ( 429 == (int) wp_remote_retrieve_response_code( $response ) ) { $reset = wp_remote_retrieve_header( $response, 'x-ratelimit-reset' ); $wait = 2; if ( $reset && is_numeric( $reset ) ) { $wait = (int) $reset - time(); } else { $retry_after = wp_remote_retrieve_header( $response, 'retry-after' ); if ( $retry_after && is_numeric( $retry_after ) ) { $wait = (int) $retry_after; } } if ( $wait < 1 ) { $wait = 1; } if ( $wait > 30 ) { $wait = 30; } sleep( $wait ); $response = wp_remote_request( $url, $args ); } if ($record) { // Notes (e.g. an ignored date) are shown in the log message of a successful request. if ( $log_note && ! is_wp_error( $response ) && (int) wp_remote_retrieve_response_code( $response ) < 300 ) { $response['response']['message'] = 'OK (' . $log_note . ')'; } adfoin_add_to_log( $response, $url, adfoin_clickup_args_for_log( $args ), $record ); } return $response; } function adfoin_clickup_args_for_log( $args ) { if ( isset( $args['headers']['Authorization'] ) ) { $args['headers']['Authorization'] = substr( (string) $args['headers']['Authorization'], 0, 6 ) . '****'; } return $args; } function adfoin_clickup_log_skipped( $record, $message ) { adfoin_add_to_log( new WP_Error( 'clickup_not_sent', $message ), 'https://api.clickup.com/api/v2/', array( 'method' => 'POST' ), $record ); } /* * Converts a form value to a ClickUp timestamp (ms). Accepts anything * strtotime() reads, in the site timezone, or a Unix timestamp. * Returns array( ms, has_time ) or null when the value can't be read. */ function adfoin_clickup_parse_date( $value ) { $value = trim( (string) $value ); if ( '' === $value ) { return null; } if ( ctype_digit( $value ) && strlen( $value ) >= 10 ) { $ms = strlen( $value ) >= 13 ? (int) $value : (int) $value * 1000; return array( $ms, true ); } $date = date_create( $value, wp_timezone() ); if ( ! $date ) { return null; } return array( (int) $date->format( 'U' ) * 1000, (bool) preg_match( '/\d{1,2}:\d{2}/', $value ) ); } /* * Sets "_date" (and "_date_time" when a time was given) on * $target. A value that can't be read is skipped and noted in $notes. */ function adfoin_clickup_set_date( &$target, $prefix, $value, $label, &$notes ) { if ( '' === trim( (string) $value ) ) { return; } $parsed = adfoin_clickup_parse_date( $value ); if ( ! $parsed ) { $notes[] = sprintf( __( '%1$s "%2$s" not recognised, left empty', 'advanced-form-integration' ), $label, $value ); return; } $target[ $prefix . '_date' ] = $parsed[0]; if ( $parsed[1] ) { $target[ $prefix . '_date_time' ] = true; } } /* * Splits a comma-separated value, trimming spaces and dropping empties. */ function adfoin_clickup_split( $value ) { return array_values( array_filter( array_map( 'trim', explode( ',', (string) $value ) ), 'strlen' ) ); } add_action( 'wp_ajax_adfoin_get_clickup_workspaces', 'adfoin_get_clickup_workspaces', 10, 0 ); /* * Get Clickup Workspaces */ function adfoin_get_clickup_workspaces() { // Security Check (capability + nonce, with isset/unslash/sanitize) adfoin_verify_nonce(); $cred_id = isset( $_POST['credId'] ) ? sanitize_text_field( wp_unslash( $_POST['credId'] ) ) : ''; $return = adfoin_clickup_request( 'team', 'GET', array(), array(), $cred_id ); if( !is_wp_error( $return ) ) { $body = json_decode( wp_remote_retrieve_body( $return ) ); if( ! isset( $body->teams ) || ! is_array( $body->teams ) ) { wp_send_json_error(); } $workspaces = wp_list_pluck( $body->teams, 'name', 'id' ); wp_send_json_success( $workspaces ); } else { wp_send_json_error(); } } add_action( 'wp_ajax_adfoin_get_clickup_spaces', 'adfoin_get_clickup_spaces', 20, 0 ); /* * Get Clickup spaces */ function adfoin_get_clickup_spaces() { // Security Check (capability + nonce, with isset/unslash/sanitize) adfoin_verify_nonce(); $cred_id = isset( $_POST['credId'] ) ? sanitize_text_field( wp_unslash( $_POST['credId'] ) ) : ''; $workspace_id = isset( $_POST['workspaceId'] ) ? sanitize_text_field( wp_unslash( $_POST['workspaceId'] ) ) : ''; $return = adfoin_clickup_request( 'team/' . $workspace_id . '/space', 'GET', array(), array(), $cred_id ); if( !is_wp_error( $return ) ) { $body = json_decode( wp_remote_retrieve_body( $return ) ); if( ! isset( $body->spaces ) || ! is_array( $body->spaces ) ) { wp_send_json_error(); } $spaces = wp_list_pluck( $body->spaces, 'name', 'id' ); wp_send_json_success( $spaces ); } else { wp_send_json_error(); } } add_action( 'wp_ajax_adfoin_get_clickup_folders', 'adfoin_get_clickup_folders', 20, 0 ); /* * Get Clickup folders */ function adfoin_get_clickup_folders() { // Security Check (capability + nonce, with isset/unslash/sanitize) adfoin_verify_nonce(); $cred_id = isset( $_POST['credId'] ) ? sanitize_text_field( wp_unslash( $_POST['credId'] ) ) : ''; $space_id = isset( $_POST['spaceId'] ) ? sanitize_text_field( wp_unslash( $_POST['spaceId'] ) ) : ''; $return = adfoin_clickup_request( 'space/' . $space_id . '/folder', 'GET', array(), array(), $cred_id ); if( !is_wp_error( $return ) ) { $body = json_decode( wp_remote_retrieve_body( $return ) ); if( ! isset( $body->folders ) || ! is_array( $body->folders ) ) { wp_send_json_error(); } $folders = wp_list_pluck( $body->folders, 'name', 'id' ); wp_send_json_success( $folders ); } else { wp_send_json_error(); } } add_action( 'wp_ajax_adfoin_get_clickup_lists', 'adfoin_get_clickup_lists', 20, 0 ); /* * Get Clickup lists */ function adfoin_get_clickup_lists() { // Security Check (capability + nonce, with isset/unslash/sanitize) adfoin_verify_nonce(); $cred_id = isset( $_POST['credId'] ) ? sanitize_text_field( wp_unslash( $_POST['credId'] ) ) : ''; $space_id = isset( $_POST['spaceId'] ) && $_POST['spaceId'] ? sanitize_text_field( wp_unslash( $_POST['spaceId'] ) ) : ''; $folder_id = isset( $_POST['folderId'] ) && $_POST['folderId'] ? sanitize_text_field( wp_unslash( $_POST['folderId'] ) ) : ''; // Folder takes precedence — both shouldn't be set, but if neither is, bail // cleanly instead of running is_wp_error() on an uninitialized variable. $return = null; if( $folder_id ) { $return = adfoin_clickup_request( 'folder/' . $folder_id . '/list', 'GET', array(), array(), $cred_id ); } elseif( $space_id ) { $return = adfoin_clickup_request( 'space/' . $space_id . '/list', 'GET', array(), array(), $cred_id ); } if( null === $return ) { wp_send_json_error(); } if( !is_wp_error( $return ) ) { $body = json_decode( wp_remote_retrieve_body( $return ) ); if( ! isset( $body->lists ) || ! is_array( $body->lists ) ) { wp_send_json_error(); } $lists = wp_list_pluck( $body->lists, 'name', 'id' ); wp_send_json_success( $lists ); } else { wp_send_json_error(); } } add_action( 'adfoin_clickup_job_queue', 'adfoin_clickup_job_queue', 10, 1 ); function adfoin_clickup_job_queue( $data ) { adfoin_clickup_send_data( $data['record'], $data['posted_data'] ); } /* * Handles sending data to ClickUp API */ function adfoin_clickup_send_data( $record, $posted_data ) { $record_data = json_decode( $record['data'], true ); if ( adfoin_check_conditional_logic( $record_data['action_data']['cl'] ?? array(), $posted_data ) ) { return; } $data = $record_data['field_data']; $task = $record['task']; $cred_id = isset( $data['credId'] ) ? $data['credId'] : ''; $workspace_id = empty( $data['workspaceId'] ) ? '' : $data['workspaceId']; $space_id = empty( $data['spaceId'] ) ? '' : $data['spaceId']; $folder_id = empty( $data['folderId'] ) ? '' : $data['folderId']; $list_id = empty( $data['listId'] ) ? '' : $data['listId']; $name = empty( $data['name'] ) ? '' : adfoin_get_parsed_values( $data['name'], $posted_data ); $description = empty( $data['description'] ) ? '' : adfoin_get_parsed_values( $data['description'], $posted_data ); $start_date = empty( $data['startDate'] ) ? '' : adfoin_get_parsed_values( $data['startDate'], $posted_data ); $due_date = empty( $data['dueDate'] ) ? '' : adfoin_get_parsed_values( $data['dueDate'], $posted_data ); $due_on_x = empty( $data["dueOnX"] ) ? "" : adfoin_get_parsed_values( $data["dueOnX"], $posted_data ); $priority_id = empty( $data['priorityId'] ) ? '' : adfoin_get_parsed_values( $data['priorityId'], $posted_data ); $assignees = empty( $data['assignees'] ) ? '' : adfoin_get_parsed_values( $data['assignees'], $posted_data ); // Backward compatibility: if no cred_id, use first available credential if( empty( $cred_id ) ) { $all_credentials = adfoin_read_credentials( 'clickup' ); if( !empty( $all_credentials ) ) { $cred_id = $all_credentials[0]['id']; } } if( $task == 'create_task' ) { if ( ! $list_id ) { adfoin_clickup_log_skipped( $record, __( 'Not sent: no ClickUp list is selected in the integration.', 'advanced-form-integration' ) ); return; } $notes = array(); $task_data = array( 'name' => $name, 'description' => $description, ); adfoin_clickup_set_date( $task_data, 'start', $start_date, __( 'Start Date', 'advanced-form-integration' ), $notes ); adfoin_clickup_set_date( $task_data, 'due', $due_date, __( 'Due Date', 'advanced-form-integration' ), $notes ); if( $due_on_x ) { $after_days = (int) $due_on_x; if( $after_days ) { $task_data['due_date'] = (int) date_create( '+' . $after_days . ' days', wp_timezone() )->format( 'U' ) * 1000; unset( $task_data['due_date_time'] ); } } if( $priority_id ) { $task_data['priority'] = (int) $priority_id; } if( $assignees ) { $assignee_ids = adfoin_clickup_assignees( $workspace_id, $assignees, $cred_id, $notes ); if( $assignee_ids ) { $task_data['assignees'] = $assignee_ids; } } $response = adfoin_clickup_request( 'list/' . $list_id . '/task', 'POST', $task_data, $record, $cred_id, implode( '; ', $notes ) ); } return; } function adfoin_get_clickup_assignee_ids( $workspace_id, $emails, $cred_id = '', &$not_found = null ) { $assignee_ids = array(); $wanted = array_map( 'strtolower', adfoin_clickup_split( $emails ) ); $not_found = $wanted; if ( ! $wanted || ! $workspace_id ) { return $assignee_ids; } $space_data = adfoin_clickup_request( 'team/' . $workspace_id, 'GET', array(), array(), $cred_id ); $body = json_decode( wp_remote_retrieve_body( $space_data ), true ); foreach ( (array) ( $body['team']['members'] ?? array() ) as $member ) { $email = strtolower( trim( $member['user']['email'] ?? '' ) ); if ( $email && in_array( $email, $wanted, true ) ) { $assignee_ids[] = $member['user']['id']; $not_found = array_diff( $not_found, array( $email ) ); } } $not_found = array_values( $not_found ); return $assignee_ids; } /* * Resolves assignee emails and notes the ones that aren't workspace members. */ function adfoin_clickup_assignees( $workspace_id, $emails, $cred_id, &$notes ) { $not_found = array(); $ids = adfoin_get_clickup_assignee_ids( $workspace_id, $emails, $cred_id, $not_found ); if ( $not_found ) { $notes[] = sprintf( __( 'not a workspace member, not assigned: %s', 'advanced-form-integration' ), implode( ', ', $not_found ) ); } return $ids; }