HEX
Server: Apache/2.4.68 (Debian)
System: Linux as-cs-widget-demo-us-central1 6.1.0-44-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.164-1 (2026-03-09) x86_64
User: root (0)
PHP: 8.2.32
Disabled: NONE
Upload Files
File: /var/www/html/wp-content/plugins/wp-recipe-maker/assets/js/admin-manage/DataTable.js
import React, { Component } from 'react';
import ReactTable from 'react-table';
import 'react-table/react-table.css';

import '../../css/admin/manage/table.scss';

import { __wprm } from 'Shared/Translations';
import ErrorBoundary from 'Shared/ErrorBoundary';
import SelectColumns from './general/SelectColumns';
import Api from 'Shared/Api';
import Totals from './general/Totals';
import { downloadToCsv } from 'Shared/CSV';

const initState = {
    data: [],
    pages: null,
    filtered: [],
    countFiltered: false,
    countTotal: false,
    loading: true,
    columns: [],
    selectedColumns: false,
    selectedRows: {},
    selectedAllRows: 0,
    exporting: false,
};

const exportPageSize = 500;

const normalizeExportText = ( value ) => {
    if ( null === value || 'undefined' === typeof value ) {
        return '';
    }

    return `${ value }`.replace( /\s+/g, ' ' ).trim();
};

const getHeaderText = ( header ) => {
    if ( 'string' === typeof header || 'number' === typeof header ) {
        return normalizeExportText( header );
    }

    if ( Array.isArray( header ) ) {
        return normalizeExportText( header.map( ( item ) => getHeaderText( item ) ).filter( ( item ) => item ).join( ' ' ) );
    }

    if ( React.isValidElement( header ) ) {
        return getHeaderText( header.props.children );
    }

    return '';
};

const stripHtml = ( value ) => {
    return `${ value }`.replace( /<[^>]*>/g, ' ' );
};

const shouldRunOnce = (() => {
    let executed = false;
    return () => {
        if ( ! executed ) {
            return true;
        }
        return false;
    };
})();

// Get default page size.
let defaultPageSize = 25;
const pageSizeOptions = [5, 10, 20, 25, 50, 100, 500];
let savedPageSize = localStorage.getItem( 'wprm-admin-manage-page-size' );

if ( savedPageSize ) {
    savedPageSize = parseInt( savedPageSize );

    if ( pageSizeOptions.includes( savedPageSize ) ) {
        defaultPageSize = savedPageSize;
    }
}

export default class DataTable extends Component {
    constructor(props) {
        super(props);

        this.state = {
            ...initState,
        };

        this.tableInnerRef = React.createRef();
        this.latestFetchState = {
            page: 0,
            pageSize: defaultPageSize,
            sorted: this.getDefaultSort(),
        };

        this.initDataTable = this.initDataTable.bind(this);
        this.refreshData = this.refreshData.bind(this);
        this.fetchData = this.fetchData.bind(this);
        this.toggleSelectRow = this.toggleSelectRow.bind(this);
        this.toggleSelectAll = this.toggleSelectAll.bind(this);
        this.getSelectedRows = this.getSelectedRows.bind(this);
        this.onColumnsChange = this.onColumnsChange.bind(this);
        this.requirementMet = this.requirementMet.bind(this);
        this.getVisibleColumns = this.getVisibleColumns.bind(this);
        this.getExportColumns = this.getExportColumns.bind(this);
        this.openExportModal = this.openExportModal.bind(this);
        this.exportToCsv = this.exportToCsv.bind(this);
        this.fetchRowsForExport = this.fetchRowsForExport.bind(this);
        this.getExportCellValue = this.getExportCellValue.bind(this);
        this.getRawColumnValue = this.getRawColumnValue.bind(this);
        this.getFormattedExportValue = this.getFormattedExportValue.bind(this);
        this.getDomRowMap = this.getDomRowMap.bind(this);
        this.getCellTextFromDom = this.getCellTextFromDom.bind(this);
        this.getColumnHeader = this.getColumnHeader.bind(this);
        this.hasActiveFilters = this.hasActiveFilters.bind(this);
        this.getMeaningfulFilters = this.getMeaningfulFilters.bind(this);
        this.isMeaningfulFilterValue = this.isMeaningfulFilterValue.bind(this);
        this.getExportFileName = this.getExportFileName.bind(this);
    }

    componentDidMount() {
        this.initDataTable();

        // check if action query paramater is set.
        const urlParams = new URLSearchParams( window.location.search );
        const action = urlParams.get( 'action' );

        if ( 'create' === action ) {
            if ( this.props.options.createButton ) {
                if ( shouldRunOnce() ) {
                    setTimeout(() => {
                        this.props.options.createButton( this );
                    });
                }
            }
        }

        // Remove query parameter again.
        if ( history.replaceState ) {
            urlParams.delete( 'action' );
            const searchString = urlParams.toString().length > 0 ? '?' + urlParams.toString() : '';
            const newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname + searchString + window.location.hash;
            history.replaceState (null, '', newUrl );
        }
    }

    componentDidUpdate( prevProps ) {
        if ( this.props.type !== prevProps.type || this.props.filter !== prevProps.filter ) {
            this.initDataTable( true );
        }
    }

    getDefaultSort() {
        if ( this.props.options.defaultSort ) {
            return this.props.options.defaultSort;
        }

        return [{
            id: 'rating' === this.props.type ? 'date' : 'id',
            desc: true,
        }];
    }

    isMeaningfulFilterValue( value ) {
        if ( null === value || 'undefined' === typeof value ) {
            return false;
        }

        if ( 'string' === typeof value ) {
            const normalizedValue = value.trim().toLowerCase();
            return '' !== normalizedValue && 'all' !== normalizedValue;
        }

        if ( Array.isArray( value ) ) {
            return value.length > 0;
        }

        return true;
    }

    getMeaningfulFilters( filters = this.state.filtered ) {
        return ( filters || [] ).filter( ( filter ) => {
            return filter && this.isMeaningfulFilterValue( filter.value );
        });
    }

    hasActiveFilters() {
        const hasInteractiveFilters = this.getMeaningfulFilters().length > 0;
        const hasFixedFilter = Array.isArray( this.props.filter ) && 2 === this.props.filter.length;

        return hasInteractiveFilters || hasFixedFilter;
    }

    getVisibleColumns() {
        return this.state.columns.filter( ( column ) => {
            return 'actions' === column.id || false === this.state.selectedColumns || this.state.selectedColumns.includes( column.id );
        });
    }

    getExportColumns( visibleColumns = this.getVisibleColumns() ) {
        return visibleColumns.filter( ( column ) => {
            return ! [ 'actions', 'bulk_edit' ].includes( column.id );
        });
    }

    getColumnHeader( column ) {
        const headerText = getHeaderText( column.Header );

        return headerText || normalizeExportText( column.id );
    }

    getCellTextFromDom( cellElement ) {
        if ( ! cellElement ) {
            return '';
        }

        const fieldElement = cellElement.querySelector( 'input, textarea, select' );

        if ( fieldElement ) {
            if ( 'SELECT' === fieldElement.tagName ) {
                if ( fieldElement.selectedOptions && fieldElement.selectedOptions.length ) {
                    return normalizeExportText(
                        Array.from( fieldElement.selectedOptions )
                            .map( ( option ) => option.textContent )
                            .join( ', ' )
                    );
                }

                return normalizeExportText( fieldElement.value );
            }

            if ( 'checkbox' === fieldElement.type ) {
                return fieldElement.checked ? __wprm( 'Yes' ) : __wprm( 'No' );
            }

            return normalizeExportText( fieldElement.value );
        }

        return normalizeExportText( cellElement.textContent );
    }

    getDomRowMap( visibleColumns ) {
        const rowMap = [];

        if ( ! this.tableInnerRef.current ) {
            return rowMap;
        }

        const rowElements = this.tableInnerRef.current.querySelectorAll( '.ReactTable .rt-tbody .rt-tr-group .rt-tr:not(.-padRow)' );

        rowElements.forEach( ( rowElement ) => {
            const cellElements = rowElement.querySelectorAll( '.rt-td' );
            const rowValues = {};

            visibleColumns.forEach( ( column, index ) => {
                rowValues[ column.id ] = this.getCellTextFromDom( cellElements[ index ] );
            });

            rowMap.push( rowValues );
        });

        return rowMap;
    }

    getRawColumnValue( row, column ) {
        if ( ! row || ! column ) {
            return '';
        }

        if ( 'function' === typeof column.accessor ) {
            try {
                return column.accessor( row );
            } catch ( error ) {
                return '';
            }
        }

        if ( 'string' === typeof column.accessor && row.hasOwnProperty( column.accessor ) ) {
            return row[ column.accessor ];
        }

        if ( column.id && row.hasOwnProperty( column.id ) ) {
            return row[ column.id ];
        }

        return '';
    }

    isRatingsObject( value ) {
        if ( ! value || 'object' !== typeof value || Array.isArray( value ) ) {
            return false;
        }

        return value.hasOwnProperty( 'average' ) || value.hasOwnProperty( 'comment_ratings' ) || value.hasOwnProperty( 'user_ratings' );
    }

    getRatingsCount( value ) {
        if ( ! this.isRatingsObject( value ) ) {
            return '';
        }

        const commentRatings = value.comment_ratings && 'object' === typeof value.comment_ratings ? parseInt( value.comment_ratings.count ) || 0 : 0;
        const userRatings = value.user_ratings && 'object' === typeof value.user_ratings ? parseInt( value.user_ratings.count ) || 0 : 0;

        return commentRatings + userRatings;
    }

    getRatingsSummary( value ) {
        if ( ! this.isRatingsObject( value ) ) {
            return '';
        }

        const average = value.average && '0' !== `${ value.average }` ? normalizeExportText( value.average ) : '';
        return average;
    }

    getFormattedExportValue( value ) {
        if ( null === value || 'undefined' === typeof value ) {
            return '';
        }

        if ( 'boolean' === typeof value ) {
            return value ? __wprm( 'Yes' ) : __wprm( 'No' );
        }

        if ( Array.isArray( value ) ) {
            return normalizeExportText(
                value
                    .map( ( item ) => this.getFormattedExportValue( item ) )
                    .filter( ( item ) => '' !== item )
                    .join( ', ' )
            );
        }

        if ( 'object' === typeof value ) {
            if ( value.hasOwnProperty( 'post_title' ) ) {
                return this.getFormattedExportValue( value.post_title );
            }

            if ( value.hasOwnProperty( 'title' ) ) {
                return this.getFormattedExportValue( value.title );
            }

            if ( value.hasOwnProperty( 'name' ) ) {
                return this.getFormattedExportValue( value.name );
            }

            if ( value.hasOwnProperty( 'label' ) ) {
                return this.getFormattedExportValue( value.label );
            }

            if ( value.hasOwnProperty( 'value' ) && ( 'string' === typeof value.value || 'number' === typeof value.value ) ) {
                return this.getFormattedExportValue( value.value );
            }

            if ( this.isRatingsObject( value ) ) {
                return this.getRatingsSummary( value );
            }

            if ( value.hasOwnProperty( 'type' ) ) {
                if ( value.hasOwnProperty( 'message' ) && 'string' === typeof value.message ) {
                    return normalizeExportText( `${ value.type }: ${ stripHtml( value.message ) }` );
                }

                return this.getFormattedExportValue( value.type );
            }

            if ( value.hasOwnProperty( 'message' ) && 'string' === typeof value.message ) {
                return normalizeExportText( stripHtml( value.message ) );
            }

            if ( value.hasOwnProperty( 'url' ) ) {
                return this.getFormattedExportValue( value.url );
            }

            // Avoid dumping entire object payloads (e.g. WP_Post) into CSV.
            return '';
        }

        return normalizeExportText( value );
    }

    getExportCellValue( row, column, domValue = '' ) {
        const rawValue = this.getRawColumnValue( row, column );

        if ( 'rating_count' === column.id ) {
            const totalRatings = this.getRatingsCount( rawValue );
            return totalRatings ? `${ totalRatings }` : '';
        }

        if ( 'rating' === column.id && this.isRatingsObject( rawValue ) ) {
            return this.getRatingsSummary( rawValue );
        }

        if ( domValue ) {
            return domValue;
        }

        return this.getFormattedExportValue( rawValue );
    }

    getExportFileName( scope ) {
        const date = new Date().toISOString().slice( 0, 10 );
        return `wprm-manage-${ this.props.options.id }-${ scope }-${ date }`;
    }

    async fetchRowsForExport( args ) {
        const sorted = this.latestFetchState.sorted && this.latestFetchState.sorted.length ? this.latestFetchState.sorted : this.getDefaultSort();
        const rows = [];
        let page = 0;
        let pages = 1;

        while ( page < pages ) {
            const response = await Api.manage.getData({
                route: this.props.options.route,
                type: this.props.options.id,
                pageSize: exportPageSize,
                page,
                sorted,
                filtered: args.filtered,
                filter: args.filter,
            });

            if ( ! response ) {
                break;
            }

            const responseRows = Array.isArray( response.rows ) ? response.rows : [];
            rows.push( ...responseRows );

            pages = response.pages ? parseInt( response.pages ) : 0;
            if ( ! pages ) {
                break;
            }

            page += 1;
        }

        return rows;
    }

    async exportToCsv( scope = 'current_view' ) {
        if ( this.state.exporting ) {
            return false;
        }

        const visibleColumns = this.getVisibleColumns();
        const exportColumns = this.getExportColumns( visibleColumns );

        if ( 0 === exportColumns.length ) {
            return false;
        }

        this.setState({
            exporting: true,
        });

        let success = false;

        try {
            let rows = [];
            let scopeKey = 'current';
            let domRowMap = [];

            switch ( scope ) {
                case 'all_filtered':
                    rows = await this.fetchRowsForExport({
                        filtered: this.getMeaningfulFilters(),
                        filter: this.props.filter,
                    });
                    scopeKey = 'filtered';
                    break;
                case 'all_rows':
                    rows = await this.fetchRowsForExport({
                        filtered: [],
                        filter: false,
                    });
                    scopeKey = 'all';
                    break;
                default:
                    rows = this.state.data;
                    domRowMap = this.getDomRowMap( visibleColumns );
                    scopeKey = 'current';
                    break;
            }

            const headers = exportColumns.map( ( column ) => this.getColumnHeader( column ) );
            const csvRows = rows.map( ( row, rowIndex ) => {
                return exportColumns.map( ( column ) => {
                    const domValue = domRowMap[ rowIndex ] && domRowMap[ rowIndex ].hasOwnProperty( column.id ) ? domRowMap[ rowIndex ][ column.id ] : '';
                    return this.getExportCellValue( row, column, domValue );
                });
            });

            downloadToCsv( this.getExportFileName( scopeKey ), headers, csvRows );
            success = true;
        } catch ( error ) {
            console.error( 'WPRM manage CSV export failed', error );
            alert( __wprm( 'Could not export this table. Please try again.' ) );
        } finally {
            this.setState({
                exporting: false,
            });
        }

        return success;
    }

    openExportModal() {
        const currentRows = Array.isArray( this.state.data ) ? this.state.data.length : 0;
        const filteredRows = false !== this.state.countFiltered ? parseInt( this.state.countFiltered ) : currentRows;
        const totalRows = false !== this.state.countTotal ? parseInt( this.state.countTotal ) : filteredRows;
        const showFilteredOption = this.hasActiveFilters() && filteredRows !== totalRows;

        if ( 'undefined' === typeof WPRM_Modal || ! WPRM_Modal || ! WPRM_Modal.open ) {
            this.exportToCsv( 'current_view' );
            return;
        }

        WPRM_Modal.open( 'manage-export-csv', {
            currentRows,
            filteredRows,
            totalRows,
            showFilteredOption,
            onConfirm: ( scope ) => this.exportToCsv( scope ),
        });
    }

    initDataTable( forceRefresh = false ) {
        // Only init when requirement is met.
        if ( ! this.requirementMet() ) {
            return;
        }

        this.latestFetchState = {
            page: 0,
            pageSize: defaultPageSize,
            sorted: this.getDefaultSort(),
        };

        // Use default selectedColumns or restore from LocalStorage.
        let selectedColumns = this.props.options.selectedColumns;

        if ( false !== selectedColumns ) {
            let savedSelectedColumns = localStorage.getItem( `wprm-admin-manage-${ this.props.options.id }-columns` );

            if ( savedSelectedColumns ) {
                savedSelectedColumns = JSON.parse(savedSelectedColumns);

                if (Array.isArray(savedSelectedColumns)) {
                    selectedColumns = savedSelectedColumns;
                }
            }
        }

        this.setState({
            ...initState,
            columns: this.props.options.columns.getColumns( this ),
            selectedColumns: selectedColumns,
        }, () => {
            if ( forceRefresh ) {
                this.refreshData();
            }
        });
    }

    toggleSelectRow(id) {
        let newSelected = { ...this.state.selectedRows };

        newSelected[id] = !newSelected[id];

        const nbrSelected = Object.values(newSelected).filter(value => value).length;
        let selectedAllRows = 2;

        if ( 0 === nbrSelected ) {
            selectedAllRows = 0;
        } else if ( this.state.data.length === nbrSelected ) {
            selectedAllRows = 1;
        }

        this.setState({
            selectedRows: newSelected,
            selectedAllRows,
        });
    }

    toggleSelectAll() {
        const bulkEditKey = 'taxonomy' === this.props.options.route ? 'term_id' : 'id';
        let newSelected = {};

        if ( 0 === this.state.selectedAllRows ) {
            for ( let row of this.state.data ) {
                newSelected[ row[ bulkEditKey ] ] = true;
            }
        }

        this.setState({
            selectedRows: newSelected,
            selectedAllRows: 0 === this.state.selectedAllRows ? 1 : 0,
        });
    }

    getSelectedRows() {
        return Object.keys(this.state.selectedRows).filter(id => this.state.selectedRows[id]).map(id => parseInt(id));
    }

    refreshData() {
        if ( this.refReactTable ) {
            this.refReactTable.fireFetchData();
        }
    }

    fetchData(state, instance) {
        const currentData = state.data;

        this.latestFetchState = {
            page: state.page,
            pageSize: state.pageSize,
            sorted: state.sorted && state.sorted.length ? state.sorted : this.getDefaultSort(),
        };

        this.setState({
            loading: true,
        }, () => {
            if ( this.requirementMet() ) {
                Api.manage.getData({
                    route: this.props.options.route,
                    type: this.props.options.id,
                    pageSize: state.pageSize,
                    page: state.page,
                    sorted: state.sorted,
                    filtered: this.state.filtered,
                    filter: this.props.filter,
                }).then(data => {
                    if ( data ) {
                        let newState = {
                            data: data.rows,
                            pages: data.pages,
                            countFiltered: data.filtered,
                            countTotal: data.total,
                            loading: false,
                        };
        
                        const bulkEditKey = 'taxonomy' === this.props.options.route ? 'term_id' : 'id';
                        if ( JSON.stringify( data.rows.map( row => row[ bulkEditKey ] ) ) !== JSON.stringify( currentData.map( row => row[ bulkEditKey ] )  ) ) {
                            newState.selectedRows = {};
                            newState.selectedAllRows = 0;
                        }
        
                        this.setState(newState);
                    }
                });
            } 
        });
    }

    onColumnsChange(id, checked) {
        let selectedColumns = [ ...this.state.selectedColumns ];

        if (checked) {
            selectedColumns.push(id);
        } else {
            selectedColumns = selectedColumns.filter(c => c !== id);
        }

        this.setState({
            selectedColumns
        });

        localStorage.setItem( `wprm-admin-manage-${ this.props.options.id }-columns`, JSON.stringify( selectedColumns ) );
    }

    requirementMet() {
        if ( this.props.options.hasOwnProperty( 'required' ) && ( ! wprm_admin.addons.hasOwnProperty( this.props.options.required ) || true !== wprm_admin.addons[ this.props.options.required ] ) ) {
            return false;
        }

        return true;
    }

    render() {
        if ( ! this.props.options ) {
            return null;
        }

        // Check if Premium requirement is met.
        if ( ! this.requirementMet() ) {
            const bundle = this.props.options.required[0].toUpperCase() + this.props.options.required.substring(1);

            return (
                <div className="wprm-admin-manage-requirement">
                    <div>*{ __wprm( 'This feature is only available in' ) }</div>
                    <a href="https://bootstrapped.ventures/wp-recipe-maker/get-the-plugin/" target="_blank">
                        { `WP Recipe Maker ${bundle} Bundle` }
                    </a>
                </div>
            );
        }

        const { data, pages, loading, exporting } = this.state;
        const selectedColumns = this.getVisibleColumns();
        const exportColumns = this.getExportColumns( selectedColumns );
        const filteredColumns = this.getMeaningfulFilters().map( ( filter ) => filter.id );

        return (
            <div className="wprm-admin-manage-page">
                <div className="wprm-admin-manage-header">
                    <SelectColumns
                        onColumnsChange={this.onColumnsChange}
                        columns={this.state.columns}
                        selectedColumns={this.state.selectedColumns}
                        filteredColumns={filteredColumns}
                    />
                    <div className="wprm-admin-manage-header-buttons">
                        {
                            ( false === this.state.selectedColumns || this.state.selectedColumns.includes( 'bulk_edit' ) )
                            && this.props.options.bulkEdit
                            && <button
                                className="button button-secondary button-compact"
                                onClick={ () => {
                                    WPRM_Modal.open( 'bulk-edit', {
                                        route: this.props.options.bulkEdit.route,
                                        type: this.props.options.bulkEdit.type,
                                        ids: this.getSelectedRows(),
                                        saveCallback: () => this.refreshData(),
                                    } );
                                }}
                                disabled={ 0 === this.getSelectedRows().length }
                            >{ __wprm( 'Bulk Edit' ) } { this.getSelectedRows().length } { 1 === this.getSelectedRows().length ? this.props.options.label.singular : this.props.options.label.plural }...</button>
                        }
                        <button
                            className="button button-secondary button-compact"
                            onClick={ this.openExportModal }
                            disabled={ loading || exporting || 0 === exportColumns.length }
                        >{ __wprm( 'Export to CSV' ) }</button>
                        {
                            this.props.options.createButton
                            ?
                            <button
                                className="button button-primary button-compact"
                                onClick={ () => this.props.options.createButton( this ) }
                            >{ `${__wprm( 'Create' )} ${ this.props.options.label.singular }` }</button>
                            :
                            null
                        }
                    </div>
                </div>
                <div className="wprm-admin-manage-table-container">
                    <ErrorBoundary module="Datatable">
                        <Totals
                            filtered={this.state.countFiltered}
                            total={this.state.countTotal}
                            filter={this.props.filter}
                            onRemoveFilter={this.props.onRemoveFilter}
                        />
                        <div className="wprm-admin-manage-table-inner" ref={ this.tableInnerRef }>
                            <ReactTable
                                ref={(refReactTable) => {this.refReactTable = refReactTable;}}
                                manual
                                columns={selectedColumns}
                                data={data}
                                pages={pages}
                                filtered={this.state.filtered}
                                onFilteredChange={ filtered => {
                                    this.setState( { filtered } );
                                } }
                                loading={ loading }
                                onFetchData={this.fetchData}
                                defaultPageSize={ defaultPageSize }
                                pageSizeOptions={ pageSizeOptions }
                                onPageSizeChange={ (pageSize) => {
                                    localStorage.setItem( 'wprm-admin-manage-page-size', pageSize );
                                }}
                                defaultSorted={ this.getDefaultSort() }
                                filterable
                                resizable={false}
                                className="wprm-admin-manage-table wprm-admin-table -highlight"
                            />
                        </div>
                    </ErrorBoundary>
                </div>
            </div>
        );
    }
}