$name, 'type' => 'PROCEDURE', 'fn' => $fn, 'args' => $args ); }; $f = function ( $name, $fn ) { return array( 'name' => $name, 'type' => 'FUNCTION', 'fn' => $fn, 'args' => array() ); }; $rows = array( $p( 'AIEO_InsertCoreProductAttributes', 'aieo_create_sp_AIEO_InsertCoreProductAttributes', array( $prefix, $attrs ) ), $p( 'AIEO_InsertProductCategoryHierarchy', 'aieo_create_sp_AIEO_InsertProductCategoryHierarchy', array( $prefix, $hierarchy ) ), $p( 'AIEO_InsertProductVitals', 'aieo_create_sp_AIEO_InsertProductVitals', array( $prefix, $vitals, $domain ) ), $p( 'AIEO_InsertVariationVitals', 'aieo_create_sp_AIEO_InsertVariationVitals', array( $prefix, $var_vit ) ), $p( 'AIEO_CombineVariationVitalsInProductVitals', 'aieo_create_sp_AIEO_CombineVariationVitalsInProductVitals', array( $prefix, $vitals, $domain ) ), $p( 'AIEO_UpdateOrdersWithUnsold', 'aieo_create_sp_AIEO_UpdateOrdersWithUnsold', array( $prefix, $orders, $products ) ), $p( 'AIEO_FindDifferences', 'aieo_create_sp_AIEO_FindDifferences', array( $prefix ) ), $p( 'AIEO_Create_Product_Catalogue_HPOS', 'aieo_create_sp_AIEO_Create_Product_Catalogue_HPOS', array( $prefix, $products ) ), $p( 'AIEO_Export_Orders_HPOS', 'aieo_create_sp_AIEO_Export_Orders_HPOS', array( $prefix, $orders ) ), $p( 'AIEO_Export_Store_Orders', 'aieo_create_sp_AIEO_Export_Store_Orders', array( $prefix, $store_ord, $erp_prod, $erp_ord, $erp_cust ) ), $p( 'AIEO_Merge_Store_Orders', 'aieo_create_sp_AIEO_Merge_Store_Orders', array( $prefix ) ), $p( 'AIEO_Export_Orders_non_HPOS', 'aieo_create_sp_AIEO_Export_Orders_non_HPOS', array( $prefix, $orders ) ), $p( 'AIEO_RenameTable', 'aieo_create_sp_AIEO_RenameTable', array( $prefix ) ), $p( 'AIEO_UpdateContentWordCount', 'aieo_create_sp_AIEO_UpdateContentWordCount', array( $prefix ) ), $p( 'AIEO_UpdateContentOutgoingLinks', 'aieo_create_sp_AIEO_UpdateContentOutgoingLinks', array( $prefix ) ), $p( 'AIEO_UpdateContentIntroPlain', 'aieo_create_sp_AIEO_UpdateContentIntroPlain', array( $prefix ) ), $p( 'AIEO_UpdateProductCentricStats', 'aieo_create_sp_UpdateProductCentricStats', array( $prefix ) ), $p( 'AIEO_UpdateCustomerCentricStdStats', 'aieo_create_sp_AIEO_UpdateCustomerCentricStdStats_Smart', array( $prefix, $orders ) ), $p( 'AIEO_UpdateCustomerCentricAdvStats', 'aieo_create_sp_AIEO_UpdateCustomerCentricAdvStats', array( $prefix, $orders ) ), $p( 'AIEO_GenerateGraphDBUUIDs', 'aieo_create_sp_AIEO_GenerateGraphDBUUIDs', array( $prefix ) ), $p( 'AIEO_GenerateGraphDBUUIDFreqs', 'aieo_create_sp_AIEO_GenerateGraphDBUUIDFreqs', array( $prefix ) ), $p( 'AIEO_PopulateProductNeeds', 'aieo_create_sp_AIEO_PopulateProductNeeds', array( $prefix ) ), $f( 'AIEO_StripHTML', 'aieo_create_function_AIEO_StripHTML' ), $f( 'AIEO_CountWords', 'aieo_create_function_AIEO_CountWords' ), $f( 'AIEO_CountLinks', 'aieo_create_function_AIEO_CountLinks' ), ); $out = array(); foreach ( $rows as $row ) { $out[ $row['name'] ] = $row; } return $out; } private static function qualified( array $row ) { if ( 'FUNCTION' === $row['type'] ) { return $row['name']; } return (string) get_option( 'aieo_site_prefix' ) . $row['name']; } public static function verify() { global $wpdb; $catalogue = self::catalogue(); $expected = array(); foreach ( $catalogue as $name => $row ) { $expected[ self::qualified( $row ) ] = $name; } if ( empty( $expected ) ) { return array( 'present' => array(), 'missing' => array() ); } $placeholders = implode( ',', array_fill( 0, count( $expected ), '%s' ) ); $found = $wpdb->get_col( $wpdb->prepare( "SELECT ROUTINE_NAME FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = DATABASE() AND ROUTINE_NAME IN ( {$placeholders} )", array_keys( $expected ) ) ); $found = array_map( 'strval', (array) $found ); $present = array(); $missing = array(); foreach ( $expected as $qualified => $name ) { $hit = false; foreach ( $found as $f ) { if ( 0 === strcasecmp( $f, $qualified ) ) { $hit = true; break; } } if ( $hit ) { $present[] = $name; } else { $missing[] = $name; } } return array( 'present' => $present, 'missing' => $missing ); } public static function can_create_routines() { global $wpdb; $probe = 'aieo_probe_' . wp_generate_password( 8, false, false ); $suppress = $wpdb->suppress_errors( true ); $hidden = $wpdb->hide_errors(); $ok = false !== $wpdb->query( "CREATE PROCEDURE `{$probe}`() SQL SECURITY INVOKER BEGIN SELECT 1; END" ); if ( $ok ) { $wpdb->query( "DROP PROCEDURE IF EXISTS `{$probe}`" ); } $wpdb->suppress_errors( $suppress ); if ( $hidden ) { $wpdb->show_errors(); } return $ok; } public static function install( $budget_secs = 0, $force = false ) { $started = microtime( true ); $catalogue = self::catalogue(); $done = $force ? array() : self::verify()['present']; $failed = array(); $pending = array(); $budget_hit = false; foreach ( $catalogue as $name => $row ) { if ( in_array( $name, $done, true ) ) { continue; } if ( $budget_secs > 0 && ( microtime( true ) - $started ) > $budget_secs ) { $budget_hit = true; $pending[] = $name; continue; } if ( ! function_exists( $row['fn'] ) ) { $failed[ $name ] = 'installer_not_loaded'; continue; } try { call_user_func_array( $row['fn'], $row['args'] ); } catch ( \Throwable $e ) { $failed[ $name ] = substr( $e->getMessage(), 0, 200 ); continue; } $done[] = $name; } $final = self::verify(); $present = $final['present']; foreach ( $final['missing'] as $name ) { if ( ! isset( $failed[ $name ] ) && ! in_array( $name, $pending, true ) ) { $failed[ $name ] = 'created_but_absent'; } } $new_state = array( 'done' => array_values( $present ), 'pending' => array_values( $pending ), 'failed' => $failed, 'total' => count( $catalogue ), 'budget_hit' => $budget_hit, 'checked_at' => time(), ); update_option( self::OPT_STATE, $new_state, false ); if ( ! empty( $pending ) && ! wp_next_scheduled( self::CRON_RESUME ) ) { wp_schedule_single_event( time() + 60, self::CRON_RESUME ); } return $new_state; } public static function resume() { if ( function_exists( 'aieo_load_sql_layer' ) ) { aieo_load_sql_layer(); } $state = self::install( self::RESUME_BUDGET_SECS ); if ( ! empty( $state['pending'] ) && ! wp_next_scheduled( self::CRON_RESUME ) ) { wp_schedule_single_event( time() + 120, self::CRON_RESUME ); } } public static function state() { $s = get_option( self::OPT_STATE, array() ); return array( 'done' => isset( $s['done'] ) && is_array( $s['done'] ) ? $s['done'] : array(), 'pending' => isset( $s['pending'] ) && is_array( $s['pending'] ) ? $s['pending'] : array(), 'failed' => isset( $s['failed'] ) && is_array( $s['failed'] ) ? $s['failed'] : array(), 'total' => isset( $s['total'] ) ? (int) $s['total'] : 0, 'budget_hit' => ! empty( $s['budget_hit'] ), 'checked_at' => isset( $s['checked_at'] ) ? (int) $s['checked_at'] : 0, ); } public static function render_notice() { if ( ! current_user_can( 'activate_plugins' ) ) { return; } $state = self::state(); if ( 0 === $state['total'] ) { return; } if ( ! empty( $state['pending'] ) ) { echo '

' . esc_html__( 'Rooster One is still setting up its database', 'ai-eshop-optimizer' ) . '

' . esc_html( sprintf( __( '%1$d of %2$d background routines are in place, and the rest are being created a few at a time so the job cannot tie up your server. Nothing is broken and there is nothing to do. Reports that depend on the remaining ones will fill in once this finishes.', 'ai-eshop-optimizer' ), count( $state['done'] ), $state['total'] ) ) . '

'; return; } if ( ! empty( $state['failed'] ) ) { $privilege = false; foreach ( $state['failed'] as $reason ) { if ( 'created_but_absent' === $reason || false !== stripos( (string) $reason, 'privilege' ) || false !== stripos( (string) $reason, 'denied' ) ) { $privilege = true; break; } } echo '

' . esc_html__( 'Rooster One could not create some database routines', 'ai-eshop-optimizer' ) . '

' . esc_html( sprintf( __( '%1$d of %2$d could not be created. Your shop works and your data is safe: everything that uses these has a slower fallback that runs in PHP instead. What you will notice is that some reports take longer, and some may stay empty on a large catalogue.', 'ai-eshop-optimizer' ), count( $state['failed'] ), $state['total'] ) ) . '

'; if ( $privilege ) { echo '

' . esc_html__( 'The usual cause is that your database user is not allowed to create stored routines, which is common on shared hosting. Ask your host to grant the CREATE ROUTINE privilege to this site\'s database user, then deactivate and reactivate Rooster One. Until then nothing further will be attempted, so this message will not keep reappearing for a new reason.', 'ai-eshop-optimizer' ) . '

'; } echo '
'; } } }