{"id":359489,"date":"2026-08-27T13:29:48","date_gmt":"2026-08-27T13:29:48","guid":{"rendered":"https:\/\/fr.wordpress.org\/plugins\/sprint-collect\/"},"modified":"2026-09-06T18:30:39","modified_gmt":"2026-09-06T18:30:39","slug":"sprint-collect","status":"publish","type":"plugin","link":"https:\/\/kaa.wordpress.org\/plugins\/sprint-collect\/","author":23539978,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.0.2","stable_tag":"1.0.2","tested":"7.1","requires":"6.5","requires_php":"8.0","requires_plugins":null,"header_name":"Sprint Collect","header_author":"Sprint Plugins","header_description":"Securely collect, review, and manage supporting documents for WordPress users.","assets_banners_color":"ebecfc","last_updated":"2026-09-06 18:30:39","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"","header_author_uri":"https:\/\/sprintplugins.fr\/","rating":0,"author_block_rating":0,"active_installs":0,"downloads":155,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.0.0":{"tag":"1.0.0","author":"sprintplugins","date":"2026-08-27 13:29:21","revision":3668927},"1.0.1":{"tag":"1.0.1","author":"sprintplugins","date":"2026-09-02 11:33:00","revision":3677875},"1.0.2":{"tag":"1.0.2","author":"sprintplugins","date":"2026-09-06 18:30:39","revision":3683818}},"upgrade_notice":[],"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3668959,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3668959,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256},"icon.svg":{"filename":"icon.svg","revision":3668959,"resolution":false,"location":"assets","locale":false}},"assets_banners":{"banner-1544x500-fr_FR.png":{"filename":"banner-1544x500-fr_FR.png","revision":3668959,"resolution":"1544x500","location":"assets","locale":"fr_FR","width":1544,"height":500},"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3668959,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250-fr_FR.png":{"filename":"banner-772x250-fr_FR.png","revision":3668959,"resolution":"772x250","location":"assets","locale":"fr_FR","width":772,"height":250},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3668959,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{"blueprint.json":{"filename":"blueprint.json","revision":3688175,"resolution":false,"location":"assets","locale":"","contents":"{\"$schema\":\"https:\\\/\\\/playground.wordpress.net\\\/blueprint-schema.json\",\"landingPage\":\"\\\/wp-admin\\\/admin.php?page=sprint-collect&tab=dashboard\",\"preferredVersions\":{\"php\":\"8.3\",\"wp\":\"latest\"},\"features\":{\"networking\":true},\"steps\":[{\"step\":\"installPlugin\",\"pluginData\":{\"resource\":\"wordpress.org\\\/plugins\",\"slug\":\"sprint-collect\"},\"options\":{\"activate\":true}},{\"step\":\"runPHP\",\"code\":\"<?php\\nrequire_once '\\\/wordpress\\\/wp-load.php';\\n\\nif ( ! class_exists( 'Sprint_Collect_Repository' ) || ! class_exists( 'Sprint_Collect_Storage' ) ) {\\n\\treturn;\\n}\\n\\nif ( get_option( 'sprint_collect_demo_seed_v1' ) ) {\\n\\treturn;\\n}\\n\\nglobal $wpdb;\\n\\n$repository = new Sprint_Collect_Repository();\\n$now        = current_time( 'mysql' );\\n$today_ts   = current_time( 'timestamp' );\\n$today      = date( 'Y-m-d', $today_ts );\\n$minus_3    = date( 'Y-m-d', strtotime( '-3 days', $today_ts ) );\\n$minus_7    = date( 'Y-m-d', strtotime( '-7 days', $today_ts ) );\\n$minus_14   = date( 'Y-m-d', strtotime( '-14 days', $today_ts ) );\\n$minus_30   = date( 'Y-m-d', strtotime( '-30 days', $today_ts ) );\\n$plus_12    = date( 'Y-m-d', strtotime( '+12 days', $today_ts ) );\\n$plus_60    = date( 'Y-m-d', strtotime( '+60 days', $today_ts ) );\\n$plus_180   = date( 'Y-m-d', strtotime( '+180 days', $today_ts ) );\\n$plus_365   = date( 'Y-m-d', strtotime( '+365 days', $today_ts ) );\\n\\n$locale = function_exists( 'determine_locale' ) ? determine_locale() : get_locale();\\n$is_fr  = 0 === strpos( strtolower( (string) $locale ), 'fr' );\\n$demo_text = static function( $en, $fr ) use ( $is_fr ) {\\n\\treturn $is_fr ? $fr : $en;\\n};\\n\\nupdate_option( 'blogname', 'Sprint Collect Demo', false );\\nupdate_option( 'blogdescription', $demo_text( 'Fictitious sports-club supporting-document workflow', 'Gestion fictive de pi\\u00e8ces justificatives pour un club sportif' ), false );\\nupdate_option( 'permalink_structure', '\\\/%postname%\\\/', false );\\n\\n$admin = get_user_by( 'login', 'admin' );\\n$admin_id = $admin ? (int) $admin->ID : 1;\\nif ( $admin_id ) {\\n\\twp_update_user(\\n\\t\\tarray(\\n\\t\\t\\t'ID'           => $admin_id,\\n\\t\\t\\t'display_name' => $demo_text( 'Demo Administrator', 'Administrateur de d\\u00e9monstration' ),\\n\\t\\t\\t'nickname'     => $demo_text( 'Demo Administrator', 'Administrateur de d\\u00e9monstration' ),\\n\\t\\t\\t'user_email'   => 'admin@example.com',\\n\\t\\t)\\n\\t);\\n}\\n\\n$create_user = static function( $login, $display_name, $first_name, $last_name ) {\\n\\t$email = $login . '@example.com';\\n\\t$user = get_user_by( 'login', $login );\\n\\tif ( ! $user ) {\\n\\t\\t$user_id = wp_create_user( $login, 'demo-password', $email );\\n\\t\\tif ( is_wp_error( $user_id ) ) {\\n\\t\\t\\treturn 0;\\n\\t\\t}\\n\\t\\t$user = get_user_by( 'id', $user_id );\\n\\t}\\n\\tif ( ! $user ) {\\n\\t\\treturn 0;\\n\\t}\\n\\twp_update_user(\\n\\t\\tarray(\\n\\t\\t\\t'ID'           => $user->ID,\\n\\t\\t\\t'display_name' => $display_name,\\n\\t\\t\\t'nickname'     => $display_name,\\n\\t\\t\\t'first_name'   => $first_name,\\n\\t\\t\\t'last_name'    => $last_name,\\n\\t\\t\\t'user_email'   => $email,\\n\\t\\t\\t'role'         => 'subscriber',\\n\\t\\t)\\n\\t);\\n\\treturn (int) $user->ID;\\n};\\n\\n$emma_id   = $create_user( 'emma.carter', 'Emma Carter', 'Emma', 'Carter' );\\n$liam_id   = $create_user( 'liam.wilson', 'Liam Wilson', 'Liam', 'Wilson' );\\n$sofia_id  = $create_user( 'sofia.martin', 'Sofia Martin', 'Sofia', 'Martin' );\\n$noah_id   = $create_user( 'noah.brown', 'Noah Brown', 'Noah', 'Brown' );\\n$maya_id   = $create_user( 'maya.lee', 'Maya Lee', 'Maya', 'Lee' );\\n$oliver_id = $create_user( 'oliver.king', 'Oliver King', 'Oliver', 'King' );\\n\\n$settings = get_option( 'sprint_collect_settings', array() );\\n$settings = is_array( $settings ) ? $settings : array();\\n$settings['display_mode']            = 'complete';\\n$settings['notify_on_upload']        = 0;\\n$settings['notify_on_status_change'] = 0;\\n$settings['admin_email']             = 'admin@example.com';\\n$settings['email_from_name']         = 'Sprint Collect Demo';\\n$settings['email_from_address']      = 'admin@example.com';\\n$settings['notification_user_ids']   = array();\\n$settings['notification_roles']      = array();\\n$settings['reminders_enabled']       = 0;\\n$settings['expiration_notice_days']  = 30;\\nupdate_option( 'sprint_collect_settings', $settings, false );\\n\\nif ( class_exists( 'Sprint_Collect_Display_Mode' ) ) {\\n\\tSprint_Collect_Display_Mode::update_site_default( Sprint_Collect_Display_Mode::COMPLETE );\\n\\tif ( $admin_id ) {\\n\\t\\tSprint_Collect_Display_Mode::update_user_preference( Sprint_Collect_Display_Mode::COMPLETE, $admin_id );\\n\\t}\\n}\\n\\n$types_by_slug = array();\\nforeach ( $repository->get_document_types() as $type ) {\\n\\t$types_by_slug[ $type->slug ] = $type;\\n}\\n\\n$configure_type = static function( $slug, $data ) use ( $repository, &$types_by_slug ) {\\n\\tif ( empty( $types_by_slug[ $slug ] ) ) {\\n\\t\\treturn 0;\\n\\t}\\n\\t$type = $types_by_slug[ $slug ];\\n\\t$id = $repository->save_document_type(\\n\\t\\tarray(\\n\\t\\t\\t'name'               => $data['name'],\\n\\t\\t\\t'slug'               => $slug,\\n\\t\\t\\t'description'        => $data['description'],\\n\\t\\t\\t'allowed_extensions' => 'pdf,jpg,jpeg,png',\\n\\t\\t\\t'max_size_mb'        => 5,\\n\\t\\t\\t'has_expiration'     => ! empty( $data['has_expiration'] ) ? 1 : 0,\\n\\t\\t\\t'has_document_date'  => ! empty( $data['has_document_date'] ) ? 1 : 0,\\n\\t\\t\\t'retention_mode'     => 'manual',\\n\\t\\t\\t'retention_days'     => 0,\\n\\t\\t\\t'source_type'        => 'upload',\\n\\t\\t\\t'source_settings'    => array(),\\n\\t\\t),\\n\\t\\t(int) $type->id\\n\\t);\\n\\tif ( $id ) {\\n\\t\\t$types_by_slug[ $slug ] = $repository->get_document_type( $id );\\n\\t}\\n\\treturn $id;\\n};\\n\\n$configure_type(\\n\\t'identity-document',\\n\\tarray(\\n\\t\\t'name' => $demo_text( 'Identity document', 'Pi\\u00e8ce d\\u2019identit\\u00e9' ),\\n\\t\\t'description' => $demo_text( 'Example identity document used to illustrate a member registration workflow.', 'Exemple de pi\\u00e8ce d\\u2019identit\\u00e9 utilis\\u00e9 pour illustrer le dossier d\\u2019inscription d\\u2019un membre.' ),\\n\\t\\t'has_expiration' => 1,\\n\\t\\t'has_document_date' => 1,\\n\\t)\\n);\\n$configure_type(\\n\\t'authorization',\\n\\tarray(\\n\\t\\t'name' => $demo_text( 'Parental authorization', 'Autorisation parentale' ),\\n\\t\\t'description' => $demo_text( 'Signed parental authorization used for a fictitious youth member.', 'Autorisation parentale sign\\u00e9e utilis\\u00e9e pour un jeune membre fictif.' ),\\n\\t\\t'has_expiration' => 0,\\n\\t\\t'has_document_date' => 1,\\n\\t)\\n);\\n$configure_type(\\n\\t'certificate',\\n\\tarray(\\n\\t\\t'name' => $demo_text( 'Medical certificate', 'Certificat m\\u00e9dical' ),\\n\\t\\t'description' => $demo_text( 'Example medical or fitness certificate for demonstration purposes.', 'Exemple de certificat m\\u00e9dical ou d\\u2019aptitude utilis\\u00e9 uniquement pour la d\\u00e9monstration.' ),\\n\\t\\t'has_expiration' => 1,\\n\\t\\t'has_document_date' => 1,\\n\\t)\\n);\\n$configure_type(\\n\\t'proof-of-address',\\n\\tarray(\\n\\t\\t'name' => $demo_text( 'Proof of address', 'Justificatif de domicile' ),\\n\\t\\t'description' => $demo_text( 'Example address document. No real address is stored in this demo.', 'Exemple de justificatif de domicile. Aucune adresse r\\u00e9elle n\\u2019est enregistr\\u00e9e dans cette d\\u00e9monstration.' ),\\n\\t\\t'has_expiration' => 0,\\n\\t\\t'has_document_date' => 1,\\n\\t)\\n);\\n$configure_type(\\n\\t'photo',\\n\\tarray(\\n\\t\\t'name' => $demo_text( 'Profile photo', 'Photo de profil' ),\\n\\t\\t'description' => $demo_text( 'Example profile photo requirement. The demo uses a neutral PDF placeholder.', 'Exemple de photo de profil demand\\u00e9e. La d\\u00e9monstration utilise un PDF neutre comme fichier de remplacement.' ),\\n\\t\\t'has_expiration' => 0,\\n\\t\\t'has_document_date' => 0,\\n\\t)\\n);\\n$configure_type(\\n\\t'signed-form',\\n\\tarray(\\n\\t\\t'name' => $demo_text( 'Signed membership form', 'Formulaire d\\u2019adh\\u00e9sion sign\\u00e9' ),\\n\\t\\t'description' => $demo_text( 'Example signed registration or volunteer form.', 'Exemple de formulaire d\\u2019inscription ou de b\\u00e9n\\u00e9volat sign\\u00e9.' ),\\n\\t\\t'has_expiration' => 0,\\n\\t\\t'has_document_date' => 1,\\n\\t)\\n);\\n\\n$type_ids = array();\\nforeach ( $repository->get_document_types() as $type ) {\\n\\t$type_ids[ $type->slug ] = (int) $type->id;\\n}\\n\\n$adult_template = $repository->save_template(\\n\\tarray(\\n\\t\\t'name' => $demo_text( 'Adult member registration', 'Inscription d\\u2019un membre adulte' ),\\n\\t\\t'description' => $demo_text( 'Typical dossier for an adult member joining a sports club.', 'Dossier type pour l\\u2019inscription d\\u2019un membre adulte dans un club sportif.' ),\\n\\t),\\n\\tarray(\\n\\t\\tarray( 'document_type_id' => $type_ids['identity-document'], 'required' => 1, 'sort_order' => 0 ),\\n\\t\\tarray( 'document_type_id' => $type_ids['proof-of-address'],   'required' => 1, 'sort_order' => 1 ),\\n\\t\\tarray( 'document_type_id' => $type_ids['photo'],              'required' => 0, 'sort_order' => 2 ),\\n\\t\\tarray( 'document_type_id' => $type_ids['signed-form'],        'required' => 1, 'sort_order' => 3 ),\\n\\t)\\n);\\n\\n$youth_template = $repository->save_template(\\n\\tarray(\\n\\t\\t'name' => $demo_text( 'Youth member registration', 'Inscription d\\u2019un jeune membre' ),\\n\\t\\t'description' => $demo_text( 'Typical dossier for a youth athlete and parental documents.', 'Dossier type pour l\\u2019inscription d\\u2019un jeune membre, avec les documents parentaux n\\u00e9cessaires.' ),\\n\\t),\\n\\tarray(\\n\\t\\tarray( 'document_type_id' => $type_ids['authorization'],     'required' => 1, 'sort_order' => 0 ),\\n\\t\\tarray( 'document_type_id' => $type_ids['certificate'],       'required' => 1, 'sort_order' => 1 ),\\n\\t\\tarray( 'document_type_id' => $type_ids['photo'],             'required' => 0, 'sort_order' => 2 ),\\n\\t\\tarray( 'document_type_id' => $type_ids['signed-form'],       'required' => 1, 'sort_order' => 3 ),\\n\\t)\\n);\\n\\n$volunteer_template = $repository->save_template(\\n\\tarray(\\n\\t\\t'name' => $demo_text( 'Volunteer onboarding', 'Dossier b\\u00e9n\\u00e9vole' ),\\n\\t\\t'description' => $demo_text( 'Simple example dossier for a volunteer joining an association.', 'Exemple simple de dossier pour une personne b\\u00e9n\\u00e9vole rejoignant une association.' ),\\n\\t),\\n\\tarray(\\n\\t\\tarray( 'document_type_id' => $type_ids['identity-document'], 'required' => 1, 'sort_order' => 0 ),\\n\\t\\tarray( 'document_type_id' => $type_ids['signed-form'],       'required' => 1, 'sort_order' => 1 ),\\n\\t\\tarray( 'document_type_id' => $type_ids['photo'],             'required' => 0, 'sort_order' => 2 ),\\n\\t)\\n);\\n\\n$dossiers = array();\\n$dossiers['emma']   = $repository->assign_template( $emma_id,   $adult_template );\\n$dossiers['liam']   = $repository->assign_template( $liam_id,   $adult_template );\\n$dossiers['sofia']  = $repository->assign_template( $sofia_id,  $youth_template );\\n$dossiers['noah']   = $repository->assign_template( $noah_id,   $youth_template );\\n$dossiers['maya']   = $repository->assign_template( $maya_id,   $volunteer_template );\\n$dossiers['oliver'] = $repository->assign_template( $oliver_id, $volunteer_template );\\n\\n$storage_ready = Sprint_Collect_Storage::ensure_base_directory();\\n$demo_pdf = base64_decode( 'JVBERi0xLjMKJZOMi54gUmVwb3J0TGFiIEdlbmVyYXRlZCBQREYgZG9jdW1lbnQgKG9wZW5zb3VyY2UpCjEgMCBvYmoKPDwKL0YxIDIgMCBSIC9GMiAzIDAgUgo+PgplbmRvYmoKMiAwIG9iago8PAovQmFzZUZvbnQgL0hlbHZldGljYSAvRW5jb2RpbmcgL1dpbkFuc2lFbmNvZGluZyAvTmFtZSAvRjEgL1N1YnR5cGUgL1R5cGUxIC9UeXBlIC9Gb250Cj4+CmVuZG9iagozIDAgb2JqCjw8Ci9CYXNlRm9udCAvSGVsdmV0aWNhLUJvbGQgL0VuY29kaW5nIC9XaW5BbnNpRW5jb2RpbmcgL05hbWUgL0YyIC9TdWJ0eXBlIC9UeXBlMSAvVHlwZSAvRm9udAo+PgplbmRvYmoKNCAwIG9iago8PAovQ29udGVudHMgOCAwIFIgL01lZGlhQm94IFsgMCAwIDU5NS4yNzU2IDg0MS44ODk4IF0gL1BhcmVudCA3IDAgUiAvUmVzb3VyY2VzIDw8Ci9Gb250IDEgMCBSIC9Qcm9jU2V0IFsgL1BERiAvVGV4dCAvSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdCj4+IC9Sb3RhdGUgMCAvVHJhbnMgPDwKCj4+IAogIC9UeXBlIC9QYWdlCj4+CmVuZG9iago1IDAgb2JqCjw8Ci9QYWdlTW9kZSAvVXNlTm9uZSAvUGFnZXMgNyAwIFIgL1R5cGUgL0NhdGFsb2cKPj4KZW5kb2JqCjYgMCBvYmoKPDwKL0F1dGhvciAoYW5vbnltb3VzKSAvQ3JlYXRpb25EYXRlIChEOjIwMjYwODI3MTM0MzQ5KzAwJzAwJykgL0NyZWF0b3IgKGFub255bW91cykgL0tleXdvcmRzICgpIC9Nb2REYXRlIChEOjIwMjYwODI3MTM0MzQ5KzAwJzAwJykgL1Byb2R1Y2VyIChSZXBvcnRMYWIgUERGIExpYnJhcnkgLSBcKG9wZW5zb3VyY2VcKSkgCiAgL1N1YmplY3QgKHVuc3BlY2lmaWVkKSAvVGl0bGUgKHVudGl0bGVkKSAvVHJhcHBlZCAvRmFsc2UKPj4KZW5kb2JqCjcgMCBvYmoKPDwKL0NvdW50IDEgL0tpZHMgWyA0IDAgUiBdIC9UeXBlIC9QYWdlcwo+PgplbmRvYmoKOCAwIG9iago8PAovRmlsdGVyIFsgL0FTQ0lJODVEZWNvZGUgL0ZsYXRlRGVjb2RlIF0gL0xlbmd0aCAyOTYKPj4Kc3RyZWFtCkdhczMvNCY8YUonRiFGLl5aKDNmJjdXLFVBWWxhSTZSPTpnWVNiRFA\\\/LmxyWCskQk5jLTorSTorQTgkaCk4OjAnTDovLV1oVD5rQyFuVEw8YFpdWjQ3Qy5NaF5mSlwhUzpgRTZyM2tRSzNWdC4oKjM0MmlpR2U8KFVAQio\\\/WjwiI2JCP2xqUmY4ZTQ\\\/LlNma2ckI3RYLjNtImRsTlVtISNxLj5lNjdWLmJtUGxzTj48c2gpWmpGYkdjYEdRYz84ZSZgP0hSaD04OEIjY1k7ODQlSmYpLi1gYTYrKmclT1JsLjxmYVtHNS9AbyxRNTJkLDcodGpEWUMwMHMoJlU6KDQrWi4xa3NGSzBUVT4mYltfKmBKbjIyKEpTOjlLPF9YJXFhZlBzVX4+ZW5kc3RyZWFtCmVuZG9iagp4cmVmCjAgOQowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwNjEgMDAwMDAgbiAKMDAwMDAwMDEwMiAwMDAwMCBuIAowMDAwMDAwMjA5IDAwMDAwIG4gCjAwMDAwMDAzMjEgMDAwMDAgbiAKMDAwMDAwMDUyNCAwMDAwMCBuIAowMDAwMDAwNTkyIDAwMDAwIG4gCjAwMDAwMDA4NTMgMDAwMDAgbiAKMDAwMDAwMDkxMiAwMDAwMCBuIAp0cmFpbGVyCjw8Ci9JRCAKWzw2OWI4MTE5OTZmNmE1YTBmY2Y1MWIyODUyM2Y4ZDM5Yz48NjliODExOTk2ZjZhNWEwZmNmNTFiMjg1MjNmOGQzOWM+XQolIFJlcG9ydExhYiBnZW5lcmF0ZWQgUERGIGRvY3VtZW50IC0tIGRpZ2VzdCAob3BlbnNvdXJjZSkKCi9JbmZvIDYgMCBSCi9Sb290IDUgMCBSCi9TaXplIDkKPj4Kc3RhcnR4cmVmCjEyOTgKJSVFT0YK' );\\n\\n$find_document = static function( $dossier_id, $slug ) use ( $wpdb ) {\\n\\t$docs  = $wpdb->prefix . 'sprint_collect_user_documents';\\n\\t$types = $wpdb->prefix . 'sprint_collect_document_types';\\n\\treturn $wpdb->get_row(\\n\\t\\t$wpdb->prepare(\\n\\t\\t\\t'SELECT ud.*, dt.slug FROM %i ud INNER JOIN %i dt ON dt.id = ud.document_type_id WHERE ud.dossier_id = %d AND dt.slug = %s LIMIT 1',\\n\\t\\t\\t$docs,\\n\\t\\t\\t$types,\\n\\t\\t\\tabsint( $dossier_id ),\\n\\t\\t\\t$slug\\n\\t\\t)\\n\\t);\\n};\\n\\n$set_document = static function( $dossier_id, $slug, $status, $args = array() ) use ( $wpdb, $repository, $admin_id, $now, $demo_pdf, $find_document ) {\\n\\t$doc = $find_document( $dossier_id, $slug );\\n\\tif ( ! $doc ) {\\n\\t\\treturn 0;\\n\\t}\\n\\n\\t$needs_file = in_array( $status, array( 'received', 'correction', 'validated', 'expired' ), true );\\n\\t$data = array(\\n\\t\\t'status'        => $status,\\n\\t\\t'admin_comment' => isset( $args['comment'] ) ? sanitize_textarea_field( $args['comment'] ) : '',\\n\\t\\t'expires_at'    => ! empty( $args['expires_at'] ) ? $args['expires_at'] : null,\\n\\t\\t'document_date' => ! empty( $args['document_date'] ) ? $args['document_date'] : null,\\n\\t\\t'updated_at'    => $now,\\n\\t\\t'validated_at'  => 'validated' === $status ? $now : null,\\n\\t\\t'validated_by'  => 'validated' === $status ? $admin_id : null,\\n\\t);\\n\\n\\tif ( $needs_file ) {\\n\\t\\t$base = Sprint_Collect_Storage::get_base_directory();\\n\\t\\t$relative_dir = absint( $doc->user_id ) . '\\\/' . absint( $dossier_id );\\n\\t\\t$target_dir = trailingslashit( $base ) . $relative_dir;\\n\\t\\twp_mkdir_p( $target_dir );\\n\\t\\t$safe_slug = sanitize_file_name( $slug );\\n\\t\\t$filename = 'demo-' . $safe_slug . '-' . absint( $doc->id ) . '.pdf';\\n\\t\\t$target = trailingslashit( $target_dir ) . $filename;\\n\\t\\tfile_put_contents( $target, $demo_pdf );\\n\\t\\t@chmod( $target, 0640 );\\n\\n\\t\\t$data['file_path']     = $relative_dir . '\\\/' . $filename;\\n\\t\\t$data['original_name'] = 'demo-' . $safe_slug . '.pdf';\\n\\t\\t$data['mime_type']     = 'application\\\/pdf';\\n\\t\\t$data['file_size']     = is_file( $target ) ? filesize( $target ) : strlen( $demo_pdf );\\n\\t\\t$data['uploaded_at']   = ! empty( $args['uploaded_at'] ) ? $args['uploaded_at'] : $now;\\n\\t}\\n\\n\\t$wpdb->update(\\n\\t\\t$wpdb->prefix . 'sprint_collect_user_documents',\\n\\t\\t$data,\\n\\t\\tarray( 'id' => absint( $doc->id ) )\\n\\t);\\n\\n\\tif ( $needs_file ) {\\n\\t\\t$repository->log(\\n\\t\\t\\t$dossier_id,\\n\\t\\t\\tabsint( $doc->id ),\\n\\t\\t\\tabsint( $doc->user_id ),\\n\\t\\t\\t'document_uploaded',\\n\\t\\t\\tarray( 'original_name' => $data['original_name'], 'demo' => true )\\n\\t\\t);\\n\\t}\\n\\n\\tif ( 'validated' === $status || 'correction' === $status || 'expired' === $status ) {\\n\\t\\t$repository->log(\\n\\t\\t\\t$dossier_id,\\n\\t\\t\\tabsint( $doc->id ),\\n\\t\\t\\t$admin_id,\\n\\t\\t\\t'document_status_changed',\\n\\t\\t\\tarray(\\n\\t\\t\\t\\t'status'  => $status,\\n\\t\\t\\t\\t'comment' => $data['admin_comment'],\\n\\t\\t\\t\\t'demo'    => true,\\n\\t\\t\\t)\\n\\t\\t);\\n\\t}\\n\\treturn absint( $doc->id );\\n};\\n\\n$upload_time_recent = date( 'Y-m-d H:i:s', strtotime( '-2 days', $today_ts ) );\\n$upload_time_week   = date( 'Y-m-d H:i:s', strtotime( '-7 days', $today_ts ) );\\n$upload_time_old    = date( 'Y-m-d H:i:s', strtotime( '-20 days', $today_ts ) );\\n\\n\\\/\\\/ Emma Carter: complete adult dossier.\\n$set_document( $dossiers['emma'], 'identity-document', 'validated', array( 'expires_at' => $plus_365, 'document_date' => $minus_30, 'uploaded_at' => $upload_time_week ) );\\n$set_document( $dossiers['emma'], 'proof-of-address', 'validated', array( 'document_date' => $minus_30, 'uploaded_at' => $upload_time_week ) );\\n$set_document( $dossiers['emma'], 'photo', 'validated', array( 'uploaded_at' => $upload_time_week ) );\\n$set_document( $dossiers['emma'], 'signed-form', 'validated', array( 'document_date' => $minus_14, 'uploaded_at' => $upload_time_week ) );\\n\\n\\\/\\\/ Liam Wilson: mixed state, including a document awaiting review.\\n$set_document( $dossiers['liam'], 'identity-document', 'received', array( 'expires_at' => $plus_365, 'document_date' => $minus_14, 'uploaded_at' => $upload_time_recent ) );\\n$set_document( $dossiers['liam'], 'proof-of-address', 'validated', array( 'document_date' => $minus_14, 'uploaded_at' => $upload_time_week ) );\\n$set_document( $dossiers['liam'], 'photo', 'missing' );\\n$set_document( $dossiers['liam'], 'signed-form', 'missing' );\\n\\n\\\/\\\/ Sofia Martin: correction requested.\\n$set_document( $dossiers['sofia'], 'authorization', 'validated', array( 'document_date' => $minus_14, 'uploaded_at' => $upload_time_week ) );\\n$set_document( $dossiers['sofia'], 'certificate', 'correction', array( 'expires_at' => $plus_180, 'document_date' => $minus_30, 'uploaded_at' => $upload_time_recent, 'comment' => $demo_text( 'Please upload a fully readable copy including the signature.', 'Merci de transmettre une copie enti\\u00e8rement lisible, comprenant la signature.' ) ) );\\n$set_document( $dossiers['sofia'], 'photo', 'validated', array( 'uploaded_at' => $upload_time_week ) );\\n$set_document( $dossiers['sofia'], 'signed-form', 'validated', array( 'document_date' => $minus_14, 'uploaded_at' => $upload_time_week ) );\\n\\n\\\/\\\/ Noah Brown: expired certificate and one missing form.\\n$set_document( $dossiers['noah'], 'authorization', 'validated', array( 'document_date' => $minus_30, 'uploaded_at' => $upload_time_old ) );\\n$set_document( $dossiers['noah'], 'certificate', 'expired', array( 'expires_at' => $minus_3, 'document_date' => $minus_30, 'uploaded_at' => $upload_time_old, 'comment' => $demo_text( 'This certificate has expired. A renewed document is required.', 'Ce certificat a expir\\u00e9. Un document renouvel\\u00e9 est n\\u00e9cessaire.' ) ) );\\n$set_document( $dossiers['noah'], 'photo', 'validated', array( 'uploaded_at' => $upload_time_old ) );\\n$set_document( $dossiers['noah'], 'signed-form', 'missing' );\\n\\n\\\/\\\/ Maya Lee: complete volunteer dossier, with an identity document expiring soon.\\n$set_document( $dossiers['maya'], 'identity-document', 'validated', array( 'expires_at' => $plus_12, 'document_date' => $minus_30, 'uploaded_at' => $upload_time_week ) );\\n$set_document( $dossiers['maya'], 'signed-form', 'validated', array( 'document_date' => $minus_14, 'uploaded_at' => $upload_time_week ) );\\n$set_document( $dossiers['maya'], 'photo', 'validated', array( 'uploaded_at' => $upload_time_week ) );\\n\\n\\\/\\\/ Oliver King: one validated document and one awaiting review.\\n$set_document( $dossiers['oliver'], 'identity-document', 'validated', array( 'expires_at' => $plus_60, 'document_date' => $minus_14, 'uploaded_at' => $upload_time_week ) );\\n$set_document( $dossiers['oliver'], 'signed-form', 'received', array( 'document_date' => $minus_7, 'uploaded_at' => $upload_time_recent ) );\\n$set_document( $dossiers['oliver'], 'photo', 'missing' );\\n\\nforeach ( $dossiers as $dossier_id ) {\\n\\tif ( $dossier_id ) {\\n\\t\\t$repository->refresh_dossier_status( $dossier_id );\\n\\t}\\n}\\n\\n\\\/\\\/ Simulate one previous reminder without sending email.\\nif ( ! empty( $dossiers['noah'] ) ) {\\n\\t$wpdb->update(\\n\\t\\t$wpdb->prefix . 'sprint_collect_dossiers',\\n\\t\\tarray( 'last_reminder_at' => date( 'Y-m-d H:i:s', strtotime( '-8 days', $today_ts ) ), 'updated_at' => $now ),\\n\\t\\tarray( 'id' => absint( $dossiers['noah'] ) )\\n\\t);\\n\\t$repository->log(\\n\\t\\t$dossiers['noah'],\\n\\t\\tnull,\\n\\t\\t$admin_id,\\n\\t\\t'reminder_sent',\\n\\t\\tarray(\\n\\t\\t\\t'source'  => 'manual',\\n\\t\\t\\t'reasons' => array( 'missing' => 1, 'correction' => 0, 'expired' => 1, 'expiring' => 0 ),\\n\\t\\t\\t'demo'    => true,\\n\\t\\t)\\n\\t);\\n}\\n\\n\\\/\\\/ Create a front-end page preconfigured to show Liam's fictitious member portal.\\n$portal_page_id = wp_insert_post(\\n\\tarray(\\n\\t\\t'post_title'   => $demo_text( 'Supporting documents \\u2013 Demo member', 'Pi\\u00e8ces justificatives \\u2013 Membre de d\\u00e9monstration' ),\\n\\t\\t'post_name'    => 'supporting-documents-demo',\\n\\t\\t'post_content' => '[sprint_collect user_id=\\\"' . absint( $liam_id ) . '\\\"]',\\n\\t\\t'post_status'  => 'publish',\\n\\t\\t'post_type'    => 'page',\\n\\t),\\n\\ttrue\\n);\\nif ( ! is_wp_error( $portal_page_id ) && $portal_page_id ) {\\n\\t$settings = get_option( 'sprint_collect_settings', array() );\\n\\t$settings = is_array( $settings ) ? $settings : array();\\n\\t$settings['portal_page_id'] = absint( $portal_page_id );\\n\\tupdate_option( 'sprint_collect_settings', $settings, false );\\n}\\n\\n\\\/\\\/ Add a second page explaining the demo and linking to the member portal.\\n$guide_content  = '<h2>' . esc_html( $demo_text( 'Sprint Collect demonstration', 'D\\u00e9monstration Sprint Collect' ) ) . '<\\\/h2>';\\n$guide_content .= '<p><strong>' . esc_html( $demo_text( 'All names, email addresses and documents in this preview are fictitious.', 'Tous les noms, adresses e-mail et documents de cet aper\\u00e7u sont fictifs.' ) ) . '<\\\/strong><\\\/p>';\\n$guide_content .= '<p>' . esc_html( $demo_text( 'This temporary WordPress Playground site demonstrates a sports-club workflow with complete, incomplete, received, correction-required and expired documents.', 'Ce site WordPress Playground temporaire pr\\u00e9sente le fonctionnement d\\u2019un club sportif avec des dossiers complets ou incomplets et des documents re\\u00e7us, \\u00e0 corriger ou expir\\u00e9s.' ) ) . '<\\\/p>';\\nif ( ! is_wp_error( $portal_page_id ) && $portal_page_id ) {\\n\\t$guide_content .= '<p><a href=\\\"' . esc_url( get_permalink( $portal_page_id ) ) . '\\\">' . esc_html( $demo_text( 'Open the demo member portal', 'Ouvrir le portail du membre de d\\u00e9monstration' ) ) . '<\\\/a><\\\/p>';\\n}\\nwp_insert_post(\\n\\tarray(\\n\\t\\t'post_title'   => $demo_text( 'Sprint Collect Demo Guide', 'Guide de d\\u00e9monstration Sprint Collect' ),\\n\\t\\t'post_name'    => 'sprint-collect-demo-guide',\\n\\t\\t'post_content' => $guide_content,\\n\\t\\t'post_status'  => 'publish',\\n\\t\\t'post_type'    => 'page',\\n\\t)\\n);\\n\\n\\\/\\\/ Disable outbound email in the temporary demo and display a clear demo notice.\\nif ( defined( 'WPMU_PLUGIN_DIR' ) ) {\\n\\twp_mkdir_p( WPMU_PLUGIN_DIR );\\n\\t$mu_code = <<<'PHP'\\n<?php\\n\\\/**\\n * Sprint Collect WordPress.org Playground demo guard.\\n *\\\/\\nadd_filter(\\n\\t'pre_wp_mail',\\n\\tstatic function( $return, $atts ) {\\n\\t\\tunset( $atts );\\n\\t\\treturn true;\\n\\t},\\n\\t10,\\n\\t2\\n);\\nadd_action(\\n\\t'admin_notices',\\n\\tstatic function() {\\n\\t\\tif ( ! isset( $_GET['page'] ) || 'sprint-collect' !== sanitize_key( wp_unslash( $_GET['page'] ) ) ) {\\n\\t\\t\\treturn;\\n\\t\\t}\\n\\t\\t$locale = function_exists( 'determine_locale' ) ? determine_locale() : get_locale();\\n\\t\\t$is_fr  = 0 === strpos( strtolower( (string) $locale ), 'fr' );\\n\\t\\tif ( $is_fr ) {\\n\\t\\t\\techo '<div class=\\\"notice notice-info\\\"><p><strong>D\\u00e9monstration Sprint Collect :<\\\/strong> toutes les personnes, adresses e-mail et tous les documents sont fictifs. L\\u2019envoi d\\u2019e-mails est d\\u00e9sactiv\\u00e9 dans cet aper\\u00e7u.<\\\/p><\\\/div>';\\n\\t\\t} else {\\n\\t\\t\\techo '<div class=\\\"notice notice-info\\\"><p><strong>Sprint Collect demo:<\\\/strong> all people, email addresses and documents are fictitious. Outbound email is disabled in this preview.<\\\/p><\\\/div>';\\n\\t\\t}\\n\\t}\\n);\\nPHP;\\n\\tfile_put_contents( trailingslashit( WPMU_PLUGIN_DIR ) . 'sprint-collect-demo-guard.php', $mu_code );\\n}\\n\\nupdate_option(\\n\\t'sprint_collect_demo_overview',\\n\\tarray(\\n\\t\\t'note'           => $demo_text( 'All demonstration content is fictitious and contains no real personal data.', 'Tout le contenu de d\\u00e9monstration est fictif et ne contient aucune donn\\u00e9e personnelle r\\u00e9elle.' ),\\n\\t\\t'portal_page_id' => is_wp_error( $portal_page_id ) ? 0 : absint( $portal_page_id ),\\n\\t\\t'demo_user_id'   => absint( $liam_id ),\\n\\t),\\n\\tfalse\\n);\\n\\nupdate_option( 'sprint_collect_demo_seed_v1', gmdate( 'c' ), false );\\nflush_rewrite_rules( false );\\n?>\"},{\"step\":\"login\",\"username\":\"admin\",\"password\":\"password\"}]}"}},"all_blocks":[],"tagged_versions":["1.0.0","1.0.1","1.0.2"],"block_files":[],"assets_screenshots":{"screenshot-1-fr_FR.png":{"filename":"screenshot-1-fr_FR.png","revision":3668959,"resolution":"1","location":"assets","locale":"fr_FR","width":1440,"height":900},"screenshot-1.png":{"filename":"screenshot-1.png","revision":3668959,"resolution":"1","location":"assets","locale":"","width":1440,"height":900},"screenshot-2-fr_FR.png":{"filename":"screenshot-2-fr_FR.png","revision":3668959,"resolution":"2","location":"assets","locale":"fr_FR","width":1440,"height":900},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3668959,"resolution":"2","location":"assets","locale":"","width":1440,"height":900},"screenshot-3-fr_FR.png":{"filename":"screenshot-3-fr_FR.png","revision":3668959,"resolution":"3","location":"assets","locale":"fr_FR","width":1440,"height":900},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3668959,"resolution":"3","location":"assets","locale":"","width":1440,"height":900},"screenshot-4-fr_FR.png":{"filename":"screenshot-4-fr_FR.png","revision":3668959,"resolution":"4","location":"assets","locale":"fr_FR","width":1440,"height":900},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3668959,"resolution":"4","location":"assets","locale":"","width":1440,"height":900},"screenshot-5-fr_FR.png":{"filename":"screenshot-5-fr_FR.png","revision":3668959,"resolution":"5","location":"assets","locale":"fr_FR","width":1440,"height":900},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3668959,"resolution":"5","location":"assets","locale":"","width":1440,"height":900}},"screenshots":{"1":"Dashboard showing active dossiers, documents awaiting review, expirations, and reminders.","2":"Dossier list with member, template, completion, and validation filters.","3":"Document review workflow with validation, correction requests, comments, and dates.","4":"Secure member upload portal for requested supporting documents.","5":"Display modes, notifications, maintenance, and WordPress privacy integration."}},"plugin_section":[],"plugin_tags":[141027,9054,396,85,1917],"plugin_category":[54],"plugin_contributors":[274079],"plugin_business_model":[],"class_list":["post-359489","plugin","type-plugin","status-publish","hentry","plugin_tags-associations","plugin_tags-documents","plugin_tags-privacy","plugin_tags-uploads","plugin_tags-users","plugin_category-security-and-spam-protection","plugin_contributors-sprintplugins","plugin_committers-sprintplugins"],"banners":{"banner":"https:\/\/ps.w.org\/sprint-collect\/assets\/banner-772x250.png?rev=3668959","banner_2x":"https:\/\/ps.w.org\/sprint-collect\/assets\/banner-1544x500.png?rev=3668959","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":"https:\/\/ps.w.org\/sprint-collect\/assets\/icon.svg?rev=3668959","icon":"https:\/\/ps.w.org\/sprint-collect\/assets\/icon.svg?rev=3668959","icon_2x":false,"generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/sprint-collect\/assets\/screenshot-1.png?rev=3668959","caption":"Dashboard showing active dossiers, documents awaiting review, expirations, and reminders."},{"src":"https:\/\/ps.w.org\/sprint-collect\/assets\/screenshot-2.png?rev=3668959","caption":"Dossier list with member, template, completion, and validation filters."},{"src":"https:\/\/ps.w.org\/sprint-collect\/assets\/screenshot-3.png?rev=3668959","caption":"Document review workflow with validation, correction requests, comments, and dates."},{"src":"https:\/\/ps.w.org\/sprint-collect\/assets\/screenshot-4.png?rev=3668959","caption":"Secure member upload portal for requested supporting documents."},{"src":"https:\/\/ps.w.org\/sprint-collect\/assets\/screenshot-5.png?rev=3668959","caption":"Display modes, notifications, maintenance, and WordPress privacy integration."}],"raw_content":"<!--section=description-->\n<p>Sprint Collect provides a complete document collection workflow for clubs, associations, and other WordPress sites.<\/p>\n\n<p>The free plugin includes:<\/p>\n\n<ul>\n<li>Reusable document types and dossier templates.<\/li>\n<li>Private file storage outside the public Media Library.<\/li>\n<li>A secure front-end upload portal for logged-in users and authorized representatives.<\/li>\n<li>Administrative review, validation, correction requests, comments, dates, and expiration tracking.<\/li>\n<li>Individual dossier assignment, closure, reopening, purging, and deletion.<\/li>\n<li>Manual reminders and standard notification emails.<\/li>\n<li>A basic activity history and maintenance tools.<\/li>\n<li>Personal data export and erasure integration with the WordPress privacy tools.<\/li>\n<li>Essential, Full, and Expert interface modes, including optional inheritance from Sprint Club.<\/li>\n<li>A documented public API for compatible add-ons.<\/li>\n<\/ul>\n\n<p>Sprint Collect works on its own and does not require an external service. Uploaded files remain on the WordPress site in a protected private directory.<\/p>\n\n<p>An optional, separately distributed Sprint Collect Pro add-on can provide automations, advanced conditions, lifecycle workflows, exports, integrations, email customization, and granular permissions. Premium implementation code is not included in this WordPress.org package.<\/p>\n\n<h3>Privacy<\/h3>\n\n<p>Sprint Collect stores dossier records, uploaded file metadata, validation information, administrative comments, dates, and private document files. These records can contain sensitive personal data selected by the site administrator.<\/p>\n\n<p>The plugin adds suggested text to the WordPress privacy policy guide and integrates with the WordPress personal data exporter and eraser. Site administrators remain responsible for defining lawful collection purposes, access rules, and retention periods.<\/p>\n\n<p>Sprint Collect does not contact an external server by itself. Optional add-ons may have their own privacy disclosures.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Upload the plugin ZIP from Plugins &gt; Add New, or install it from WordPress.org.<\/li>\n<li>Activate Sprint Collect.<\/li>\n<li>Open Sprint Collect and configure document types and templates.<\/li>\n<li>Create or select a portal page containing the <code>[sprint_collect]<\/code> shortcode.<\/li>\n<li>Assign templates to WordPress users.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"where%20are%20uploaded%20documents%20stored%3F\"><h3>Where are uploaded documents stored?<\/h3><\/dt>\n<dd><p>Files are stored in a protected private directory under the WordPress uploads directory. They are not added to the public Media Library.<\/p><\/dd>\n<dt id=\"does%20the%20plugin%20require%20sprint%20club%3F\"><h3>Does the plugin require Sprint Club?<\/h3><\/dt>\n<dd><p>No. Sprint Club is optional. When installed, it can provide centralized navigation and interface-mode inheritance.<\/p><\/dd>\n<dt id=\"does%20the%20free%20plugin%20expire%20or%20impose%20a%20quota%3F\"><h3>Does the free plugin expire or impose a quota?<\/h3><\/dt>\n<dd><p>No. The free features are complete and do not expire. Sprint Collect Pro is a separate optional add-on.<\/p><\/dd>\n<dt id=\"are%20files%20removed%20when%20the%20plugin%20is%20uninstalled%3F\"><h3>Are files removed when the plugin is uninstalled?<\/h3><\/dt>\n<dd><p>Not by default. Administrators can explicitly enable permanent data removal before uninstalling the plugin.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.0.2<\/h4>\n\n<ul>\n<li>Fixed delegated and self-service front-end uploads so authorized family representatives can upload and replace documents for the selected member.<\/li>\n<li>Dossier document queries now load the dossier owner and lifecycle fields required to evaluate upload permissions correctly.<\/li>\n<li>Restored the dossier template context used by conditional document messages in the member portal.<\/li>\n<\/ul>\n\n<h4>1.0.1<\/h4>\n\n<ul>\n<li>Fixed the activation flow for premium add-ons so they can be activated before their independent license is entered.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial WordPress.org release.<\/li>\n<li>Secure private document collection, review, validation, and reminders.<\/li>\n<li>Added WordPress personal data export and erasure support.<\/li>\n<li>Added Essential, Full, and Expert interface modes with Sprint Club integration.<\/li>\n<\/ul>","raw_excerpt":"Securely collect, review, and manage supporting documents for WordPress users.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/kaa.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/359489","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kaa.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/kaa.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/kaa.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=359489"}],"author":[{"embeddable":true,"href":"https:\/\/kaa.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/sprintplugins"}],"wp:attachment":[{"href":"https:\/\/kaa.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=359489"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/kaa.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=359489"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/kaa.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=359489"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/kaa.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=359489"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/kaa.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=359489"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/kaa.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=359489"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}