PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
← All changes | app/Repositories/ItineraryRepository.php +58 -81 3.0.4 → 3.0.16 View file →
@@ -33,8 +33,17 @@
33 33 * @throws \InvalidArgumentException If day exists and $allowExisting is false
34 34 */
35 35 public function getOrCreateDay(int $tripId, int $dayNumber, ?string $dayTitle = null, ?string $dayDescription = null, bool $allowExisting = true): int
36 36 {
37 + // The method body uses $wpdb and $tableDays directly for the day
38 + // update/insert below. The cached-query closures declare their own copies,
39 + // but the body never did — so $wpdb was null ("Call to a member function
40 + // update() on null") and $tableDays was empty ("Incorrect table name '')".
41 + // This broke saving an activity onto an existing, titled day and silently
42 + // mis-created new days. Declare both here.
43 + global $wpdb;
44 + $tableDays = TripItineraryDaysTable::getTableName();
45 +
37 46 // Use QueryCache for caching day existence checks
38 47 $cacheKey = Cache::KEY_DAY_EXISTS . "_{$tripId}_day_{$dayNumber}";
39 48
40 49 $existingDay = $this->cacheQueryResult($cacheKey, function() use ($tripId, $dayNumber) {
@@ -87,9 +96,10 @@
87 96 $existingDaysList = implode(', ', $existingDays);
88 97
89 98 throw new \InvalidArgumentException(
90 99 sprintf(
91 - __('Day %s already exists for this trip. Please use day %d instead.', 'yatra'),
100 + /* translators: 1: comma-separated list of existing day numbers, 2: next available day number. */
101 + __('Day %1$s already exists for this trip. Please use day %2$d instead.', 'yatra'),
92 102 $existingDaysList,
93 103 $nextDay
94 104 )
95 105 );
@@ -143,9 +153,9 @@
143 153 */
144 154 public function createEntry(array $data): int
145 155 {
146 156 global $wpdb;
147 - $tableEntries = $this->getTableName(); // yatra_new_trip_itinerary_day_entry
157 + $tableEntries = $this->getTableName(); // yatra_trip_itinerary_day_entry
148 158 $tableDays = TripItineraryDaysTable::getTableName();
149 159
150 160 // Determine if this is a day entry or an activity entry
151 161 // Day entries have no item_type_id and item_id (or they are explicitly 0)
@@ -882,8 +892,16 @@
882 892
883 893 return $result !== false;
884 894 }
885 895
896 + // Explicit 'day' mode must never fall through to delete an activity.
897 + // Day ids and activity ids come from different tables and can collide
898 + // numerically; if the day no longer exists (e.g. already removed in
899 + // another tab), bail rather than silently deleting a same-id activity.
900 + if ($mode === 'day') {
901 + return false;
902 + }
903 +
886 904 // Check if this is an activity entry ID (from entries table)
887 905 $activityEntry = $wpdb->get_row(
888 906 $wpdb->prepare("SELECT * FROM `{$tableEntries}` WHERE id = %d", $id)
889 907 );
@@ -902,100 +920,58 @@
902 920 return false;
903 921 }
904 922
905 923 /**
906 - * Bulk delete entries
907 - * @param array $ids Array of entry IDs to delete
924 + * Bulk delete itinerary days and/or activity entries.
925 + *
926 + * Days and activities live in two different tables with independent
927 + * auto-increment id spaces, so a single flat list of ids is ambiguous
928 + * (the same number can be a valid day id *and* a valid activity id). The
929 + * caller therefore tells us which is which: `$dayIds` are day-table ids
930 + * (deleting one removes the day row and all of its activities) and `$ids`
931 + * are activity-entry ids. Each id is routed through the single-item
932 + * {@see self::delete()} with an explicit mode so the correct table is
933 + * always used.
934 + *
935 + * @param array $ids Activity entry ids (day_entry table)
936 + * @param array $dayIds Day ids (days table); the day and its activities are removed
908 937 * @return array ['deleted' => count, 'failed' => count]
909 938 */
910 - public function bulkDelete(array $ids): array
939 + public function bulkDelete(array $ids, array $dayIds = []): array
911 940 {
912 - global $wpdb;
913 - $tableEntries = $this->getTableName();
914 -
915 - $tableDays = TripItineraryDaysTable::getTableName();
916 -
917 - if (empty($ids)) {
918 - return ['deleted' => 0, 'failed' => 0];
919 - }
920 -
921 - // Sanitize IDs
922 - $ids = array_map('intval', $ids);
923 - $ids = array_filter($ids, function($id) {
924 - return $id > 0;
925 - });
926 -
927 - if (empty($ids)) {
928 - return ['deleted' => 0, 'failed' => 0];
929 - }
930 -
931 941 $deleted = 0;
932 942 $failed = 0;
933 - $processedDayIds = []; // Track days we've already processed
934 943
935 - foreach ($ids as $id) {
944 + // Delete whole days first — this also removes every activity that
945 + // belongs to the day, so any of those activity ids that also appear in
946 + // $ids become harmless no-ops below.
947 + $dayIds = array_unique(array_filter(array_map('intval', $dayIds), static function ($id) {
948 + return $id > 0;
949 + }));
950 + foreach ($dayIds as $dayId) {
936 951 try {
937 - // Get the entry to check if it's a day entry
938 - $entry = $wpdb->get_row(
939 - $wpdb->prepare("SELECT day_id, item_type_id, item_id FROM `{$tableEntries}` WHERE id = %d", $id)
940 - );
941 -
942 - if (!$entry) {
952 + if ($this->delete($dayId, 'day')) {
953 + $deleted++;
954 + } else {
943 955 $failed++;
944 - continue;
945 956 }
957 + } catch (\Throwable $e) {
958 + $failed++;
959 + }
960 + }
946 961
947 - $dayId = (int) $entry->day_id;
948 - $isDayEntry = ($entry->item_type_id === null || $entry->item_type_id === 0) &&
949 - ($entry->item_id === null || $entry->item_id === 0);
950 -
951 - // If this is a day entry, delete all entries for this day
952 - if ($isDayEntry) {
953 - // Skip if we've already processed this day
954 - if (in_array($dayId, $processedDayIds)) {
955 - continue;
956 - }
957 -
958 - $processedDayIds[] = $dayId;
959 -
960 - // Get all entry IDs for this day
961 - $dayEntryIds = $wpdb->get_col(
962 - $wpdb->prepare("SELECT id FROM `{$tableEntries}` WHERE day_id = %d", $dayId)
963 - );
964 -
965 - if (!empty($dayEntryIds)) {
966 - // Delete images for all entries
967 - $placeholders = implode(',', array_fill(0, count($dayEntryIds), '%d'));
968 - $wpdb->query(
969 - $wpdb->prepare(
970 - "DELETE FROM `{$tableImages}` WHERE entry_id IN ($placeholders)",
971 - ...$dayEntryIds
972 - )
973 - );
974 -
975 - // Delete all entries for this day
976 - $wpdb->delete($tableEntries, ['day_id' => $dayId], ['%d']);
977 -
978 - // Delete the day itself
979 - $wpdb->delete($tableDays, ['id' => $dayId], ['%d']);
980 - }
981 -
962 + // Delete standalone activity entries.
963 + $ids = array_unique(array_filter(array_map('intval', $ids), static function ($id) {
964 + return $id > 0;
965 + }));
966 + foreach ($ids as $id) {
967 + try {
968 + if ($this->delete($id, 'activity')) {
982 969 $deleted++;
983 970 } else {
984 - // For activity entries, just delete the entry and its images
985 - // Delete related images
986 - $wpdb->delete($tableImages, ['entry_id' => $id], ['%d']);
987 -
988 - // Delete entry
989 - $result = $wpdb->delete($tableEntries, ['id' => $id], ['%d']);
990 -
991 - if ($result !== false) {
992 - $deleted++;
993 - } else {
994 - $failed++;
995 - }
971 + $failed++;
996 972 }
997 - } catch (\Exception $e) {
973 + } catch (\Throwable $e) {
998 974 $failed++;
999 975 }
1000 976 }
1001 977
@@ -1061,8 +1037,9 @@
1061 1037 $tableEntries,
1062 1038 [
1063 1039 'trip_id' => (int) $day->trip_id,
1064 1040 'day_id' => $dayId,
1041 + /* translators: %d: itinerary day number. */
1065 1042 'title' => $day->title ?: sprintf(__('Day %d', 'yatra'), (int) $day->day_number),
1066 1043 'description' => '',
1067 1044 'location' => null,
1068 1045 'duration' => null,